从子类1调用超类构造函数的C ++规则是什么?
例如,我知道在Java中,您必须将其作为子类构造函数的第一行(如果不这样做,则假定对no-arg超级构造函数进行隐式调用 - 如果不是,则给出编译错误那是不见了。)
答案 0 :(得分:845)
如果基类构造函数没有参数,则会自动为您调用它们。如果要使用参数调用超类构造函数,则必须使用子类的构造函数初始化列表。与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
}
};
答案 1 :(得分:218)
在C ++中,在输入构造函数之前,会为您调用所有超类和成员变量的无参数构造函数。如果你想传递它们的参数,有一个单独的语法称为“构造函数链接”,它看起来像这样:
class Sub : public Base
{
Sub(int x, int y)
: Base(x), member(y)
{
}
Type member;
};
如果此时运行的任何东西抛出,之前完成构造的基础/成员都会调用它们的析构函数,并且异常将被重新调用给调用者。如果要在链接期间捕获异常,则必须使用函数try块:
class Sub : public Base
{
Sub(int x, int y)
try : Base(x), member(y)
{
// function body goes here
} catch(const ExceptionType &e) {
throw kaboom();
}
Type member;
};
在这种形式中,请注意try块是函数的主体,而不是在函数体内;这允许它捕获由隐式或显式成员和基类初始化以及函数体期间抛出的异常。但是,如果函数catch块没有抛出不同的异常,则运行时将重新抛出原始错误;初始化期间的异常无法被忽略。
答案 2 :(得分:48)
在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_的值更可取,因为这样可以节省额外的赋值成本。
请记住,数据成员始终按照在类定义中声明它们的顺序进行初始化,而不管它们在初始化列表中的顺序如何。为了避免在数据成员相互依赖时可能出现的奇怪错误,您应该始终确保成员的顺序在初始化列表和类定义中是相同的。出于同样的原因,基类构造函数必须是初始化列表中的第一项。如果省略它,那么将自动调用基类的默认构造函数。在这种情况下,如果基类没有默认构造函数,则会出现编译器错误。
答案 3 :(得分:20)
每个人都通过初始化列表提到了构造函数调用,但是没有人说可以从派生成员的构造函数体中显式调用父类的构造函数。例如,请参阅问题Calling a constructor of the base class from a subclass' constructor body。 关键是如果在派生类的主体中使用对父类或超类构造函数的显式调用,实际上这只是创建父类的实例,而不是在派生对象上调用父类构造函数。在派生类的对象上调用父类或超类构造函数的唯一方法是通过初始化列表而不是派生类构造函数体。所以也许它不应该被称为“超类构造函数调用”。我把这个答案放在这里是因为有人可能会感到困惑(就像我一样)。
答案 4 :(得分:19)
如果你有一个没有参数的构造函数,它将在派生类构造函数执行之前被调用。
如果要使用参数调用基础构造函数,则必须在派生构造函数中显式写入,如下所示:
class base
{
public:
base (int arg)
{
}
};
class derived : public base
{
public:
derived () : base (number)
{
}
};
如果不在C ++中调用父构造函数,则无法构造派生类。如果它是非arg C'tor,则会自动发生,如果您直接调用派生构造函数,如上所示,或者您的代码将无法编译,则会发生这种情况。
答案 5 :(得分:19)
将值传递给父构造函数的唯一方法是通过初始化列表。初始化列表使用:实现,然后是类列表和要传递给该类构造函数的值。
Class2::Class2(string id) : Class1(id) {
....
}
还要记住,如果你有一个没有父类参数的构造函数,它将在子构造函数执行之前自动调用。
答案 6 :(得分:11)
如果基础构造函数中有默认参数,则会自动调用基类。
using namespace std;
class Base
{
public:
Base(int a=1) : _a(a) {}
protected:
int _a;
};
class Derived : public Base
{
public:
Derived() {}
void printit() { cout << _a << endl; }
};
int main()
{
Derived d;
d.printit();
return 0;
}
输出为:1
答案 7 :(得分:9)
CDerived::CDerived()
: CBase(...), iCount(0) //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0)
{
//construct body
}
答案 8 :(得分:6)
当一个类派生自多个类时,没有人提到构造函数调用的顺序。在推导类时,序列如上所述。
答案 9 :(得分:0)
如果您只是想将所有构造函数参数传递给基类(=父),这是一个最小的示例。
这使用模板将带有1、2或3个参数的每个构造函数调用转发到父类time.time()
。
代码
std::string
输出
#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;
}
更新:使用可变参数模板
泛化为n个参数并简化
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)
到
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;
}