在C ++中,如果我们有这个类
class Uncopyable{
public:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
然后我们有一个派生类
class Dervied: private Uncopyable{
};
我的问题是:当编译器在派生类中生成默认的复制构造函数和赋值运算符时,为什么不会生成编译时错误?生成的代码不会尝试访问基类私有成员吗?
答案 0 :(得分:4)
C ++ 11 12.8 / 7状态"如果类定义没有显式声明复制构造函数,则会隐式声明一个。"所以Dervied
有一个隐式声明的复制构造函数。 12.8 / 11说:
隐式声明的复制/移动构造函数是其类的
inline public
成员。如果X
具有以下内容,则X
类的默认复制/移动构造函数被定义为已删除(8.4.3):
具有非平凡对应构造函数的变体成员,
X
是类似联合的类,类类型
M
(或其数组)的非静态数据成员,由于重载解析(13.3)而无法复制/移动,因为应用于M
的相应构造函数,导致模糊或从默认构造函数中删除或无法访问的函数,无法复制/移动的直接或虚拟基类
B
因为重载解析(13.3),应用于B
的相应构造函数,会导致歧义或函数从默认构造函数中删除或无法访问,具有析构函数的任何直接或虚拟基类或非静态数据成员,该析构函数已从默认构造函数中删除或无法访问,
用于复制构造函数,rvalue引用类型的非静态数据成员,或
用于移动构造函数,非静态数据成员或直接或虚拟基类,其类型不具有移动构造函数,并且不易于复制。
具体来说,第三个项目符号适用:Dervied
具有无法复制的直接基类Uncopyable
,因为重载解析会导致Dervied
:: {{1}无法访问的函数}}。因此,Dervied(const Dervied&)
隐式声明的复制构造函数被声明为已删除,当且仅当调用该复制构造函数时,才会导致编译时错误。
答案 1 :(得分:3)
当编译器在派生类中生成默认的复制构造函数和赋值运算符时,为什么不会产生编译时错误?
因为只有在编译代码需要它们时,编译器才会生成它们。使用包含复制构造函数和/或赋值运算符的派生类编写一些代码,您将看到正在查找的编译时错误。
答案 2 :(得分:2)
派生类将继承私有拷贝构造函数,但除非您复制派生类型的对象,否则不需要使用它,如本例所示。
编译器不会自动生成构造函数/运算符,除非它们使用 和没有其他构造函数/运算符可用于执行该操作(即复制操作可以在移动操作就足够的某些情况下使用)。后一种陈述产生以下一套规则。
以下是自动生成某些成员函数的规则:
默认构造函数(如果没有显式声明其他构造函数)
如果没有移动构造函数或移动赋值运算符,则复制构造函数 明确声明。如果析构函数被声明为生成a 不建议使用复制构造函数。
如果没有副本,则移动构造函数 构造函数,移动赋值运算符或析构函数是显式的 声明。
如果没有移动构造函数或移动赋值,则复制赋值运算符 运算符是显式声明的。如果声明了析构函数 不推荐生成复制赋值运算符。
如果没有复制构造函数,复制赋值运算符,则移动赋值运算符 或析构函数是显式声明的。
析构
该列表取自this Wikipedia page。
答案 3 :(得分:1)
继承中的private
使private
成为Derived
,它仍然可以看到它们,使用Derived
的类不能。{/ p>
答案 4 :(得分:1)
一个类不能在另一个类上调用私有方法,但它可以继承尽可能多的编码。此代码仅包含Derived中Uncopyable的成员函数。
想象一下,如果你编写了一个继承自std :: vector的类。您仍然可以擦除,插入,推送和执行所有这些操作。因为它们都是公共或受保护的向量成员函数,所以它们又调用特定于实现的私有函数来执行管理内存等低级操作。您在此派生类中的代码无法直接调用这些内存管理函数。这用于确保向量的创建者可以自由地更改内部细节,而不会破坏您对该类的使用。
如果您的示例是代码实际上的样子,那么这是用于制作无法复制的内容的常见模式。它会使以下代码产生编译器错误:
Derived Foo;
Derived Bar;
Foo = Bar
它还会使代码在以下内容中引发错误:
int GetAnswer(Derived bar)
{ /* Do something with a Derived */ }
Derived Foo;
int answer = GetAnser(Foo);
此示例失败,因为foo的副本已作为GetAnswer函数中的参数传递并传递。
有些东西可能无法复制的原因有很多。我经历过的最常见的是对象管理一些低级资源,一个文件,一个opengl上下文,一个音频输出等等...想象一下,如果你有一个管理日志文件的类。如果它在解构器中关闭了文件,那么当销毁副本时原始文件会发生什么。
编辑:将Uncopyable类传递给函数,通过引用传递它。以下功能不会复制:
int GetAnswer(Derived& bar)
{ /* Do something with a Derived */ }
Derived Foo;
int answer = GetAnser(Foo);
如果所有构造函数都是私有的并且实例化了类,那么它也会导致编译器错误。但即使所有成员函数甚至构造函数都是私有的,并且该类从未实例化,这将是有效的编译代码。
编辑:具有构造函数的类的原因是可能有其他方法来构造它,或者它可能具有静态成员函数或类函数。
有时工厂用于构建没有明显构造函数的对象。这些可能具有创建umakeable类实例所需的任何魔法的功能。我见过的最常见的只是有另一个公开的构造函数,但不是一个显而易见的地方。我也看到工厂作为unconstructable类的朋友类,所以他们可以调用构造函数,我已经看到代码手动旋转内存并将指针转换为内存的实例。所有这些模式都用于确保正确创建类,而不仅仅是C ++提供的保证。
我见过的一个更常见的模式是类中的静态成员函数。
class uncreateable
{
uncreateable() {}
public:
static int GetImportantAnswer();
};
可以看出,我不仅需要创建类的实例来调用GetImportantAnswer(),而且如果需要的话我也无法创建实例。我可以使用以下代码调用此代码:
int answer;
answer = uncreateable::GetImportantAnswer();
编辑:拼写和语法
答案 5 :(得分:0)
嗯,实际上这个程序不能用g ++编译:
#include <iostream>
using namespace std;
class Uncopyable{
public:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&) {cout<<"in parent copy constructor";}
Uncopyable& operator=(const Uncopyable&) { cout << "in parent assignment operator";}
};
class Derived: private Uncopyable{
};
int main() {
Derived a;
Derived b = a;
}
编译器输出:
$ g++ 23183322.cpp
23183322.cpp:10:88: warning: control reaches end of non-void function [-Wreturn-type]
Uncopyable& operator=(const Uncopyable&) { cout << "in parent assignment operator";}
^
23183322.cpp:13:7: error: base class 'Uncopyable' has private copy constructor
class Derived: private Uncopyable{
^
23183322.cpp:9:5: note: declared private here
Uncopyable(const Uncopyable&) {cout<<"in parent copy constructor";}
^
23183322.cpp:19:15: note: implicit copy constructor for 'Derived' first required here
Derived b = a;
^
1 warning and 1 error generated.