如何使用基类的构造和分配运营商在C++?
-
22-07-2019 - |
题
我有一个类 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
's构造和运营商?我特别想要避免改写的分配运营商,因为它具有以访问所有的 B
's的私人部件的变量。
解决方案
您可以显式调用构造函数和赋值运算符:
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;
}
};
其他提示
简短的回答:是的,你需要重复的工作中d
长答案:
如果您的派生类“d”不包含任何新的成员变量,则默认版本(由编译器生成的应该只是罚款)。默认复制构造会调用父类的拷贝构造函数和默认的赋值运算符将调用父赋值运算符。
但是,如果你的类“d”包含资源,那么你就需要做一些工作。
我觉得你的拷贝构造函数有点奇怪:
B(const B& b){(*this) = b;}
D(const D& d){(*this) = d;}
通常拷贝构造链,使得它们复制从所述基部向上构成。在这里,因为你所呼叫的赋值操作符拷贝构造函数必须调用默认的构造函数为默认首先初始化从下往上的对象。然后你去再次使用赋值运算符。这似乎是相当低效的。
现在,如果你这样做你是从下往上(或自上而下)拷贝赋值但似乎你很难做到这一点,提供了有力的异常保证。如果在任何时候资源无法复制,你抛出一个异常对象将处于不确定的状态(这是一件坏事)。
通常,我见过它做的其他方式。结果 赋值运算符是在拷贝构造函数和交换的定义。这是因为它可以更容易提供强异常保证。我不认为你将能够做这种方式左右(我可能是错的)提供了强有力的保证。
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 */
}
};
你最有可能有一个缺陷在设计中(暗示: 切片, 实体的语义 vs 值语义).具有完全的复制/值语义 在一个对象从一个多形态层次结构往往不是一个需要在所有。如果你想要它提供公正的情况下,一个可能以后需要,这意味着你永远不需要它。使基类不可以复制而不是(通过继承提升::noncopyable为实例),这是所有。
唯一正确的解决方案时需要这样的 真的 似乎是的 信封信的成语, 或者小的框架,从上的文章 定期对象 肖恩的父母和亚历山大*斯特潘诺夫请参考.所有其他的解决方案会给你麻烦的切片和/或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::=
}
需要注意的是,作为履行机构指出,如果定义任何构造函数,编译器不会为你生成默认的构造函数和包括拷贝构造函数。
在原始代码是错误的:
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
将要删除的unexisting存储器。