从派生类调用基类构造函数的C++规则是什么?
例如,我知道在Java中,您必须作为子类构造函数的第一行执行此操作(如果不这样做,则假定对无参数超级构造函数的隐式调用-如果缺少,则会给您一个编译错误)。
从派生类调用基类构造函数的C++规则是什么?
例如,我知道在Java中,您必须作为子类构造函数的第一行执行此操作(如果不这样做,则假定对无参数超级构造函数的隐式调用-如果缺少,则会给您一个编译错误)。
当前回答
在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块没有抛出不同的异常,则运行时将重新抛出原始错误;初始化期间的异常不能忽略。
其他回答
CDerived::CDerived()
: CBase(...), iCount(0) //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0)
{
//construct body
}
如果在基构造函数中有默认参数,则将自动调用基类。
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
如果您只想将所有构造函数参数传递给基类(=父类),这里是一个最小的示例。
这使用模板将每个带有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;
}
在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块没有抛出不同的异常,则运行时将重新抛出原始错误;初始化期间的异常不能忽略。
在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_赋值更可取,因为这样可以节省额外的赋值成本。
请记住,无论数据成员在初始化列表中的顺序如何,数据成员总是按照它们在类定义中声明的顺序进行初始化。为了避免在数据成员相互依赖时可能出现的奇怪错误,您应该始终确保初始化列表和类定义中成员的顺序相同。出于同样的原因,基类构造函数必须是初始化列表中的第一项。如果完全忽略它,那么将自动调用基类的默认构造函数。在这种情况下,如果基类没有默认构造函数,则会出现编译器错误。