提问人:Igor 提问时间:8/4/2009 最后编辑:AzeemIgor 更新时间:4/24/2018 访问量:87627
如何在C++中使用基类的构造函数和赋值运算符?
How to use base class's constructors and assignment operator in C++?
问:
我有一个带有一组构造函数和一个赋值运算符的类。B
在这里:
class B
{
public:
B();
B(const string& s);
B(const B& b) { (*this) = b; }
B& operator=(const B & b);
private:
virtual void foo();
// and other private member variables and functions
};
我想创建一个继承类,该类将覆盖函数,并且不需要进行其他更改。D
foo()
但是,我希望拥有相同的构造函数集,包括复制构造函数和赋值运算符:D
B
D(const D& d) { (*this) = d; }
D& operator=(const D& d);
我是否必须将它们全部重写,或者有没有办法使用 的构造函数和运算符?我特别想避免重写赋值运算符,因为它必须访问所有私有成员变量。D
B
B
答:
简短的回答:是的,您需要重复 D 中的工作
长答案:
如果派生类“D”不包含新成员变量,则默认版本(由编译器生成)应该可以正常工作。默认的 Copy 构造函数将调用父复制构造函数,默认赋值运算符将调用父赋值运算符。
但是,如果你的类“D”包含资源,那么你将需要做一些工作。
我发现你的复制构造函数有点奇怪:
B(const B& b){(*this) = b;}
D(const D& d){(*this) = d;}
通常,复制构造函数链,以便它们从基础开始复制构造。在这里,由于您调用的是赋值运算符,因此复制构造函数必须调用默认构造函数,以默认首先从下到上初始化对象。然后,使用赋值运算符再次向下。这似乎效率很低。
现在,如果你做一个作业,你是自下而上(或自上而下)复制的,但你似乎很难做到这一点,并提供一个强有力的例外保证。如果在任何时候资源复制失败,并且您抛出异常,则对象将处于不确定状态(这是一件坏事)。
通常我看到它以相反的方式完成。
赋值运算符是根据 copy 构造函数和 swap 定义的。这是因为它使提供强异常保证变得更加容易。我不认为你能够通过这样做来提供强有力的保证(我可能是错的)。
class X
{
// If your class has no resources then use the default version.
// Dynamically allocated memory is a resource.
// If any members have a constructor that throws then you will need to
// write your owen version of these to make it exception safe.
X(X const& copy)
// Do most of the work here in the initializer list
{ /* Do some Work Here */}
X& operator=(X const& copy)
{
X tmp(copy); // All resource all allocation happens here.
// If this fails the copy will throw an exception
// and 'this' object is unaffected by the exception.
swap(tmp);
return *this;
}
// swap is usually trivial to implement
// and you should easily be able to provide the no-throw guarantee.
void swap(X& s) throws()
{
/* Swap all members */
}
};
即使您从 X 派生出类 D,也不会影响此模式。
诚然,您需要通过对基类进行显式调用来重复一些工作,但这相对微不足道。
class D: public X
{
// Note:
// If D contains no members and only a new version of foo()
// Then the default version of these will work fine.
D(D const& copy)
:X(copy) // Chain X's copy constructor
// Do most of D's work here in the initializer list
{ /* More here */}
D& operator=(D const& copy)
{
D tmp(copy); // All resource all allocation happens here.
// If this fails the copy will throw an exception
// and 'this' object is unaffected by the exception.
swap(tmp);
return *this;
}
// swap is usually trivial to implement
// and you should easily be able to provide the no-throw guarantee.
void swap(D& s) throws()
{
X::swap(s); // swap the base class members
/* Swap all D members */
}
};
评论
new int(5)
int
std::string
std::string
你的设计很可能存在缺陷(提示:切片、实体语义与值语义)。通常根本不需要对来自多态层次结构的对象具有完整的副本/值语义。如果您想提供它以防万一以后可能需要它,这意味着您永远不会需要它。改为使基类不可复制(例如,通过继承 boost::noncopyable),仅此而已。
当这种需求真正出现时,唯一正确的解决方案是信封字母的成语,或者是 Sean Parent 和 Alexander Stepanov IIRC 关于规则对象的文章中的小框架。所有其他解决方案都会给您带来切片和/或 LSP 的麻烦。
关于这个主题,另请参阅 C++CoreReference C.67: C.67:基类应禁止复制,如果需要“复制”,则应提供虚拟克隆。
您必须重新定义所有非默认构造函数或复制构造函数。您不需要重新定义复制构造函数或赋值运算符,因为编译器提供的运算符(根据标准)将调用所有基版本:
struct base
{
base() { std::cout << "base()" << std::endl; }
base( base const & ) { std::cout << "base(base const &)" << std::endl; }
base& operator=( base const & ) { std::cout << "base::=" << std::endl; }
};
struct derived : public base
{
// compiler will generate:
// derived() : base() {}
// derived( derived const & d ) : base( d ) {}
// derived& operator=( derived const & rhs ) {
// base::operator=( rhs );
// return *this;
// }
};
int main()
{
derived d1; // will printout base()
derived d2 = d1; // will printout base(base const &)
d2 = d1; // will printout base::=
}
请注意,正如 sbi 所指出的,如果您定义了任何构造函数,编译器将不会为您生成默认构造函数,其中包括复制构造函数。
评论
derived
as sbi noted, if you define any constructor the compiler
可以显式调用构造函数和赋值运算符:
class Base {
//...
public:
Base(const Base&) { /*...*/ }
Base& operator=(const Base&) { /*...*/ }
};
class Derived : public Base
{
int additional_;
public:
Derived(const Derived& d)
: Base(d) // dispatch to base copy constructor
, additional_(d.additional_)
{
}
Derived& operator=(const Derived& d)
{
Base::operator=(d);
additional_ = d.additional_;
return *this;
}
};
有趣的是,即使您没有显式定义这些函数(然后它使用编译器生成的函数),这也有效。
class ImplicitBase {
int value_;
// No operator=() defined
};
class Derived : public ImplicitBase {
const char* name_;
public:
Derived& operator=(const Derived& d)
{
ImplicitBase::operator=(d); // Call compiler generated operator=
name_ = strdup(d.name_);
return *this;
}
};
评论
Base(const Base&)
std::forward()
std::forward()
)
原始代码错误:
class B
{
public:
B(const B& b){(*this) = b;} // copy constructor in function of the copy assignment
B& operator= (const B& b); // copy assignment
private:
// private member variables and functions
};
通常,不能根据复制分配来定义复制构造函数,因为复制分配必须释放资源,而复制构造函数不会!!
要理解这一点,请考虑:
class B
{
public:
B(Other& ot) : ot_p(new Other(ot)) {}
B(const B& b) {ot_p = new Other(*b.ot_p);}
B& operator= (const B& b);
private:
Other* ot_p;
};
为避免内存泄漏,复制分配首先必须删除 ot_p 指向的内存:
B::B& operator= (const B& b)
{
delete(ot_p); // <-- This line is the difference between copy constructor and assignment.
ot_p = new Other(*b.ot_p);
}
void f(Other& ot, B& b)
{
B b1(ot); // Here b1 is constructed requesting memory with new
b1 = b; // The internal memory used in b1.op_t MUST be deleted first !!!
}
因此,复制构造函数和复制赋值是不同的,因为前者将构造和对象构造到初始化的内存中,而后者则必须先释放现有内存,然后才能构造新对象。
如果执行本文中最初建议的操作:
B(const B& b){(*this) = b;} // copy constructor
您将删除一个不存在的内存。
评论
foo
using B::operator=;