【问题标题】:What is the difference between 'using' a parent class constructor and creating a new one in c++?“使用”父类构造函数和在 C++ 中创建新构造函数有什么区别?
【发布时间】:2021-10-08 17:01:41
【问题描述】:

所以最近我一直在用 C++ 编写一个基本的数学库,并决定制作模板类和继承的、专门的类,例如 Vector。

我在链接父类的默认构造函数时遇到了一些问题,而无需创建一个新构造函数。 我可以看到 using 关键字还允许我使用我想要的任何基类构造函数,但另一种解决方案(下面的那个)似乎更优雅。

    template<typename type>
    class Vector3D : public Vector<type, 3>
    {
    public:
        /// Telling compiler to use constructors of parent class Vector
        using Vector<type, 3>::Vector;

        Vector3D() : KMath::Vector<type, 3>() {}

当我看到这两种方法都有效时,我很好奇它们之间有什么区别,是否有更好/更广泛使用的方法?

编辑:为了明确起见,我将在此处粘贴父类:

    template<typename type, unsigned int SIZE>
    class Vector
    {
    protected:
        type vectorTuple[SIZE];

    public:
        explicit Vector()
        {
            unsigned int i;
            for (i = 0; i < SIZE; ++i)
            {
                vectorTuple[i] = 0.0;
            }
        }

        explicit Vector(type arr[SIZE])
        {
            unsigned int i;
            for (i = 0; i < SIZE; ++i)
            {
                vectorTuple[i] = arr[i];
            }
        }
    ...
    };

我的问题不仅在于调用了默认构造函数,还在于另一个构造函数,因为如果没有 using 关键字,如果我尝试编译器会返回错误

double arr[3] = {1, 2, 3};
Vector3D<double> vec(arr);

如果没有 using 关键字或在派生类中创建相同类型的构造函数,代码显然无法工作。我在网上的某个地方看到过这个解决方案,只是想看看它是否有任何影响。

【问题讨论】:

  • 如果您认为越冗长越优雅,请务必进行大量可避免的冗余输入。我不确定为什么你甚至需要 using 作为默认构造函数。
  • 我不认为您的示例说明了您在链接方面遇到的问题。在这种情况下,您可以同时省略 usingVector3D 构造函数,而不会改变含义。基类是一个模板,因此如果您遇到链接器问题,它与实例化有关;例如正文不存在于标题中。我可以想到一个会出现这个问题的案例,但它涉及多个源文件,并且显式编写的派生类构造函数的主体也在 CPP 文件(不是 H 文件)中。

标签: c++ inheritance constructor using


【解决方案1】:

如果我们比较使用using 与手动重新声明相同的构造函数并将它们显式转发到基类,那么就结果代码而言,两者实际上是等效的。但是,将代码库视为一个活生生的、不断发展的实体时,确实存在差异:

它改变了代码所表达的两个类的构造之间的逻辑关系。

以下两个陈述不是同一个意思:

  • “此类具有与其父级相同的构造函数,并且可能还有更多。”
  • “这个类有这些构造函数,其中一些恰好与它的父类现在相同。”

例如:假设在 6 个月后引入了 Vector&lt;size, type&gt; 的新构造函数。使用usingVector3&lt;type&gt; 将隐式继承该构造函数,无需任何额外的重构。这是否可取是决定哪种方法合适的区别因素。

编写好的代码不仅仅是让程序以某种方式可靠地运行。这也是为了确保程序的行为在很长一段时间内保持稳定。

【讨论】:

  • 它只是不会“改变逻辑关系”。它使 base 的构造函数可见,仅此而已。可以使 base 的构造函数可见并且有额外的子构造函数。
  • @SergeyA 我将 OP 的问题解释为比较使用 using 与手动调度相同的构造函数的替代方案,我的答案是围绕这个范围。我将添加说明。
【解决方案2】:

TL;DR - 在你的 sn-p 中,你也不需要。您的构造函数是默认构造函数,不使用参数,您可以轻松删除 using 声明以及显式默认构造函数。

在更一般的情况下,基构造函数的using 声明使其在子类中可见(否则它会被隐式子类的默认构造函数隐藏)。

当您使用它时,基类构造函数在子类中变得可用,并允许通过调用具有匹配签名的基类构造函数之一来构造子类。

例如:

struct Base {
    Base(int );
};

struct Derived : Base {
    // using Base::Base;
};

void foo() {
    Derived d{1}; // Fails unless using directive is uncommented above
}

当派生类不需要执行它们自己的任何初始化并且只是想提供可用的基础构造时,这种方法是可行的。

请注意,using base 的构造函数不会阻止派生类创建自己的类。例如:

struct Base {
    Base(int );
};

struct Derived : Base {
    using Base::Base;
    Derived(const char* str);
};

在上面的例子中,Dervived d{1} 会调用Base:Base(int ),而Derived d{"hello"} 会调用Derived::Derived(const char*)

另一方面,当有一些与派生类构造函数相关联的逻辑时,从派生构造函数显式调用基构造函数是可用的——即没有默认初始化程序的非默认成员初始化、非平凡的主体等。例如:

struct Base {
    Base(int );
};

struct Derived : Base {
    Derived(int x) : Base(x), own(x + 5) { }
    int own;
};

在这种情况下,Derived 构造函数需要做两件事 - 使用 int 参数调用 Base 并初始化 own。所以using Base 的构造函数不合适,因为它会使own 未初始化。

【讨论】:

  • 嗨。感谢您的解释,我认为它很好地向我解释了整个想法。确实,我的示例非常糟糕,因为我什至没有显示 Vector 类,并且这样可以更好地显示问题(Vector 有多个默认构造函数)。但是现在没有必要了,因为你的解释对我来说似乎很有意义并且真的用尽了这个话题。再次感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-23
  • 1970-01-01
  • 2019-08-30
  • 2021-10-19
  • 2011-04-16
  • 1970-01-01
相关资源
最近更新 更多