从派生类调用基类构造函数的C++规则是什么?
例如,我知道在Java中,您必须作为子类构造函数的第一行执行此操作(如果不这样做,则假定对无参数超级构造函数的隐式调用-如果缺少,则会给您一个编译错误)。
从派生类调用基类构造函数的C++规则是什么?
例如,我知道在Java中,您必须作为子类构造函数的第一行执行此操作(如果不这样做,则假定对无参数超级构造函数的隐式调用-如果缺少,则会给您一个编译错误)。
当前回答
如果您有一个没有参数的构造函数,则在执行派生类构造函数之前将调用它。
如果要使用参数调用基构造函数,则必须在派生构造函数中显式地这样写:
class base
{
public:
base (int arg)
{
}
};
class derived : public base
{
public:
derived () : base (number)
{
}
};
如果不调用C++中的父构造函数,就无法构造派生类。如果它是非参数C',则会自动发生这种情况;如果您直接调用派生构造函数(如上所示),或者代码不会编译,则会发生这种情况。
其他回答
在C++中,有一个构造函数初始化列表的概念,您可以也应该在这里调用基类的构造函数,也应该在那里初始化数据成员。初始化列表位于构造函数签名之后,冒号之后,构造函数主体之前。假设我们有a级:
class A : public B
{
public:
A(int a, int b, int c);
private:
int b_, c_;
};
然后,假设B有一个接受int的构造函数,a的构造函数可能如下所示:
A::A(int a, int b, int c)
: B(a), b_(b), c_(c) // initialization list
{
// do something
}
如您所见,基类的构造函数在初始化列表中被调用。顺便说一句,初始化初始化列表中的数据成员比在构造函数的主体内为b_和c_赋值更可取,因为这样可以节省额外的赋值成本。
请记住,无论数据成员在初始化列表中的顺序如何,数据成员总是按照它们在类定义中声明的顺序进行初始化。为了避免在数据成员相互依赖时可能出现的奇怪错误,您应该始终确保初始化列表和类定义中成员的顺序相同。出于同样的原因,基类构造函数必须是初始化列表中的第一项。如果完全忽略它,那么将自动调用基类的默认构造函数。在这种情况下,如果基类没有默认构造函数,则会出现编译器错误。
每个人都提到通过初始化列表调用构造函数,但没有人说可以从派生成员的构造函数体显式调用父类的构造函数。例如,请参阅从子类的构造函数体调用基类的构造函数的问题。重点是,如果在派生类的主体中使用对父类或超级类构造函数的显式调用,这实际上只是创建父类的实例,而不是在派生对象上调用父类构造函数。在派生类对象上调用父类或超级类构造函数的唯一方法是通过初始化列表,而不是在派生类构造函数体中。因此,也许它不应该被称为“超类构造函数调用”。我把这个答案放在这里是因为有人可能会(像我一样)感到困惑。
如果您只想将所有构造函数参数传递给基类(=父类),这里是一个最小的示例。
这使用模板将每个带有1、2或3个参数的构造函数调用转发给父类std::string。
Code
Live版本
#include <iostream>
#include <string>
class ChildString: public std::string
{
public:
template<typename... Args>
ChildString(Args... args): std::string(args...)
{
std::cout
<< "\tConstructor call ChildString(nArgs="
<< sizeof...(Args) << "): " << *this
<< std::endl;
}
};
int main()
{
std::cout << "Check out:" << std::endl;
std::cout << "\thttp://www.cplusplus.com/reference/string/string/string/" << std::endl;
std::cout << "for available string constructors" << std::endl;
std::cout << std::endl;
std::cout << "Initialization:" << std::endl;
ChildString cs1 ("copy (2)");
char char_arr[] = "from c-string (4)";
ChildString cs2 (char_arr);
std::string str = "substring (3)";
ChildString cs3 (str, 0, str.length());
std::cout << std::endl;
std::cout << "Usage:" << std::endl;
std::cout << "\tcs1: " << cs1 << std::endl;
std::cout << "\tcs2: " << cs2 << std::endl;
std::cout << "\tcs3: " << cs3 << std::endl;
return 0;
}
输出
Check out:
http://www.cplusplus.com/reference/string/string/string/
for available string constructors
Initialization:
Constructor call ChildString(nArgs=1): copy (2)
Constructor call ChildString(nArgs=1): from c-string (4)
Constructor call ChildString(nArgs=3): substring (3)
Usage:
cs1: copy (2)
cs2: from c-string (4)
cs3: substring (3)
更新:使用变量模板
推广到n个参数并简化
template <class C>
ChildString(C arg): std::string(arg)
{
std::cout << "\tConstructor call ChildString(C arg): " << *this << std::endl;
}
template <class C1, class C2>
ChildString(C1 arg1, C2 arg2): std::string(arg1, arg2)
{
std::cout << "\tConstructor call ChildString(C1 arg1, C2 arg2, C3 arg3): " << *this << std::endl;
}
template <class C1, class C2, class C3>
ChildString(C1 arg1, C2 arg2, C3 arg3): std::string(arg1, arg2, arg3)
{
std::cout << "\tConstructor call ChildString(C1 arg1, C2 arg2, C3 arg3): " << *this << std::endl;
}
to
template<typename... Args>
ChildString(Args... args): std::string(args...)
{
std::cout
<< "\tConstructor call ChildString(nArgs="
<< sizeof...(Args) << "): " << *this
<< std::endl;
}
如果基类构造函数没有参数,它们将自动为您调用。如果要使用参数调用超类构造函数,则必须使用子类的构造函数初始化列表。与Java不同,C++支持多重继承(无论好坏),因此基类必须按名称引用,而不是“super()”。
class SuperClass
{
public:
SuperClass(int foo)
{
// do something with foo
}
};
class SubClass : public SuperClass
{
public:
SubClass(int foo, int bar)
: SuperClass(foo) // Call the superclass constructor in the subclass' initialization list.
{
// do something with bar
}
};
有关构造函数初始化列表的更多信息,请点击此处。
如果您有一个没有参数的构造函数,则在执行派生类构造函数之前将调用它。
如果要使用参数调用基构造函数,则必须在派生构造函数中显式地这样写:
class base
{
public:
base (int arg)
{
}
};
class derived : public base
{
public:
derived () : base (number)
{
}
};
如果不调用C++中的父构造函数,就无法构造派生类。如果它是非参数C',则会自动发生这种情况;如果您直接调用派生构造函数(如上所示),或者代码不会编译,则会发生这种情况。