[翻译] Effective C++, 3rd Edition, Item 6: 如果你不想使用 compiler-generated functions(编译器生成函数),就明确拒绝

时间:2022-09-06 12:48:31

Item 6: 如果你不想使用 compiler-generated functions(编译器生成函数),就明确拒绝

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

房地产代理商出售房屋,服务于这样的代理商的软件系统自然要有一个 class(类)来表示被出售的房屋:

class HomeForSale { ... };

每一个房地产代理商都会很快指出,每一件房产都是独特的——没有两件是完全一样的。在这种情况下,为 HomeForSale object(对象)做一个 copy(拷贝)的想法就令人不解了。你怎么能拷贝一个独一无二的东西呢?因此最好让类似这种企图拷贝 HomeForSale object(对象)的行为不能通过编译:

HomeForSale h1;

HomeForSale h2;

HomeForSale h3(h1);               // attempt to copy h1 — should
                                  // not compile!

h1 = h2;                          // attempt to copy h2 — should
                                  // not compile!

唉,防止这种编译的方法并非那么简单易懂。通常,如果你不希望一个 class(类)支持某种功能,你可以简单地不声明赋予它这种功能的函数。这个策略对于 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)不起作用,因为,就象 Item 5 中指出的,如果你不声明它们,而有人又想调用它们,编译器就会替你声明它们。

这就限制了你。如果你不声明 copy constructor(拷贝构造函数)或 copy assignment operator(拷贝赋值运算符),编译器也可以替你生成它们。你的 class(类)还是会支持 copying(拷贝)。另一方面,如果你声明了这些函数,你的 class(类)依然会支持 copying(拷贝)。而我们此时的目的却是 prevent copying(防止拷贝)!

解决这个问题的关键是所有的编译器生成的函数都是 public(公有)的。为了防止生成这些函数,你必须自己声明它们,但是你没有理由把它们声明为 public(公有)的。相反,应该将 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)声明为 private(私有)的。通过显式声明一个 member function(成员函数),可以防止编译器生成它自己的版本,而且将这个函数声明为 private(私有)的,可以防止别人调用它。

通常,这个方案并不十分保险,因为 member(成员)和 friend functions(友元函数)还是能够调用你的 private 函数。换句话说,除非你十分聪明地不 define(定义)它们。那么,当有人不小心地调用了它们,在 link-time(连接时)会出现错误。这个窍门——声明 member functions(成员函数)为 private 却故意不去实现它——确实很好,在 C++ 的 iostreams 库里,就有几个类用此方法 prevent copying(防止拷贝)。比如,看一下你用的标准库的实现中 ios_basebasic_iossentry 的 definitions(定义),你就会看到 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)被声明为 private 而且没有被定义的情况。

将这个窍门用到 HomeForSale 上,很简单:

class HomeForSale {
public:
  ...

private:
  ...
  HomeForSale(const HomeForSale&);            // declarations only
  HomeForSale& operator=(const HomeForSale&);
};

你会注意到,我省略了 functions' parameters(函数参数)的名字。这不是必须的,只是一个普通的惯例。毕竟,函数不会被实现,更少会被用到,有什么必要指定参数名呢?

对于上面的 class definition(类定义),编译器将阻止客户拷贝 HomeForSale objects(对象)的企图,如果你不小心在 member(成员)或 friend function(友元函数)中这样做了,连接程序会提出*。

将 link-time error(连接时错误)提前到编译时间也是可行的(早发现错误毕竟比晚发现好),通过不在 HomeForSale 本身中声明 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)为 private,而是在一个为 prevent copying(防止拷贝)而特意设计的 base class(基类)中声明。这个 base class(基类)本身非常简单:

class Uncopyable {
protected:                                   // allow construction
  Uncopyable() {}                            // and destruction of
  ~Uncopyable() {}                           // derived objects...

private:
  Uncopyable(const Uncopyable&);             // ...but prevent copying
  Uncopyable& operator=(const Uncopyable&);
};

为了阻止拷贝 HomeForSale objects(对象),我们现在必须让它从 Uncopyable 继承:

class HomeForSale: private Uncopyable {      // class no longer
  ...                                        // declares copy ctor or
};                                           // copy assign. operator

这样做是因为,如果有人——甚至是 member(成员)或 friend function(友元函数)——试图拷贝一个 HomeForSale objects(对象),编译器将试图生成一个 copy constructor(拷贝构造函数)和一个 copy assignment operator(拷贝赋值运算符)。就象 Item 12 解释的,这些函数的 compiler-generated versions(编译器生成版)会试图调用 base class(基类)的相应函数,而这些调用将被拒绝,因为在 base class(基类)中,拷贝操作是 private(私有)的。

Uncopyable 的实现和使用包含一些微妙之处,比如,从 Uncopyable 继承不必是 public(公有)的(参见 Item 3239),而且 Uncopyable 的 destructor(析构函数)不必是 virtual(虚拟)的(参见 Item 7)。因为 Uncopyable 不包含数据,所以它符合 Item 39 描述的 empty base class optimization(空基类优化)的条件,但因为它是 base class(基类),此项技术的应用不能引入 multiple inheritance(多继承)(参见 Item 40)。反过来说,multiple inheritance(多继承)有时会使 empty base class optimization(空基类优化)失效(还是参见 Item 39)。通常,你可以忽略这些微妙之处,而且仅仅像此处演示的这样来使用 Uncopyable,因为它的工作就像在做广告。你还可以使用在 Boost(参见 Item 55)中的一个可用版本。那个 class(类)名为 noncopyable。那是一个好东西,我只是发现那个名字有点儿 un-(不……)嗯…… nonnatural(非自然)。

Things to Remember

  • 为了拒绝编译器自动提供的机能,将相应的 member functions(成员函数)声明为 private,而且不要给出 implementations(实现)。使用一个类似 Uncopyable 的 base class(基类)是方法之一。