【问题标题】:Is this a good way of using inheritance?这是使用继承的好方法吗?
【发布时间】:2018-02-23 22:17:26
【问题描述】:

这是使用继承的好方法吗?

#include <iostream>

namespace Test::A {
    class A {
        char const * const this_message;

    public:
        constexpr A(char const * const message) noexcept
            : this_message(message) {
        }

        constexpr char const * message() const noexcept {
            return this_message;
        }
    };

    struct B : public A {
        constexpr B(char const * const message) noexcept
            : A(message) {
        }
    };
}

int main() {

    std::cout << sizeof(Test::A::A) << '\n';
    std::cout << sizeof(Test::A::B) << '\n';

    std::cout << Test::A::B("Test 1").message() << '\n';

    try {

        char const * const Test4 = "Test 4";

        // Uncomment / comment to toggle between
        //throw Test::A::B("Test 1");
        throw Test::A::B(Test4);

    } catch (Test::A::A const a) {
        // I'm passing 'a' by copy because the size of 'a'
        // will always be either 4 or 8 bytes / it's faster this way.
        constexpr auto message = Test::A::B("Test 3").message();

        std::cout << a.message() << " " << message << '\n';
    }

    return 0;
}

我有 2 个问题:

A) 这是使用继承和...的好方法吗?

B) 这个程序是否正确、格式正确且没有未定义的行为?

我想创建自己的简单的两级异常层次结构(只有一个基类和一些派生类),并且我正在尝试找到一种无需任何虚拟方法的方法。

以下代码确实有效,并且它似乎以正确的顺序调用了析构函数,即使派生类上没有什么可破坏的。然而,我对这项工作的内部原理感到有些困惑。

我知道如果我想通过基指针删除派生类,我需要一个虚拟析构函数,但我没有考虑支持这种多态性。

我知道,通过从B 调用message 方法,我只是从A 调用该方法,而它又只返回成员变量this_message,它可以在@987654326 中初始化@ 或B

将对象传递给catch调用时,B对象是否通过切片过程变成A?但是因为B只是初始化了A的一个成员/和A的状态一样,所以最后都解决了。

这个想法是通过传递包含行号、文件位置等的静态字符数组在宏中初始化 A 类及其派生类。

【问题讨论】:

  • 这只是我喜欢保持的一个习惯,我会尽我所能const。不过,这并不能回答我的问题。
  • 尚不清楚为什么B 需要存在。此外,我不确定在函数参数中通过值传递一个小的且可简单复制的类型比通过引用传递快一点的经验法则对于异常处理程序参数也是如此。
  • 有了这样一个微不足道的例子,关于它是否是一个“好”的继承例子,没有什么好说的。除此之外,抛出最终源自std::exception 的异常,并始终通过引用捕获它们,否则您可能最终会分割异常并丢弃信息。
  • B 需要存在,因为它将代表一种全新的异常类型,具有完全不同的上下文含义。
  • 在 C++ 中使用宏是(你在最后一句中说的就是你要去的地方)在 C++ 中通常是非常糟糕的技术。

标签: c++ inheritance polymorphism


【解决方案1】:

我知道如果我想通过基指针删除派生类,我需要一个虚拟析构函数,但我没有考虑支持这种多态性。

如果您是一个项目的唯一程序员,那么这种妥协可能是可行的,但您需要与每个加入项目的人进行沟通并强制执行,因此您很难扩展或重复使用您编写的代码。

将对象传递给catch调用时,B对象是否通过切片过程变成了A?但是因为 B 只是初始化了 A 的一个成员 / 和 A 的状态相同,所以最后一切都解决了。

是的,通过切片。是的,它挂在一起。

这个想法是通过传递包含行号、文件位置等的静态字符数组在宏中初始化 A 类及其派生类。

这是另一个问题/陷阱:人们习惯于 std::exception 在内部 std::string 中捕获文本,因此必须警告其他程序员不要做像 Test::A::B(my_message_in_a_string.c_str());Test::A::B(my_ostringstream.str()) 这样的事情,使其成为可能将有用的消息放入异常类中要困难得多。

A) 这是使用继承和...的好方法吗?

它允许您选择按基类(无论是通过引用还是切片)或派生类型进行捕获,因此继承增加了有用的功能。

B) 这个程序是否正确、格式正确且没有未定义的行为?

确实如此,但由于缺乏虚拟功能和使用切片,它在维护方面很脆弱,并且难以扩展。

【讨论】:

  • 感谢您提供周到且结构合理的答案。 :) 这是我的一个严格的个人项目,我可以在其中决定自己的规则和约定。我想要的只是一个不依赖外部库的简单轻量级的异常类。这种可能不太常用的策略对我来说非常有效。
【解决方案2】:

您使用的是标准库 std::cout,所以绝对应该从 std::exception 派生异常。

你的类的名字应该恰当地命名,比如SomethingException。

用指针初始化你的类似乎是错误的,即使它指向 .const 部分中的某些东西。您已经初始化了堆栈中将要展开的指针。该指针将在您的类实例中的副本执行之前死亡,因为它被抛出。它可以工作,因为地址是 .const,但它有异味,会导致未来的维护者摸不着头脑。

所以: A - 不是一种好的继承形式。 B - 尽管有臭味,但行为已定义;-)

【讨论】:

  • 关于名称,那些只是占位符,std::cout 只是为了在屏幕上查看结果。就像我在之前的 cmets 中所说的那样,所有异常都将通过一个宏来初始化并抛出,该宏将构造一个静态编译时 char 数组,从而将指针和展开问题减少到零。谢谢你的回答:)
猜你喜欢
  • 2015-07-16
  • 1970-01-01
  • 2017-11-10
  • 1970-01-01
  • 2012-06-02
  • 2011-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多