复制构造,赋值操作符,const重要性

原文地址:http://www.cplusplus.com/articles/jsmith1/

什么是复制构造?

复制构造是类或结构体的一种特殊的拷贝已存在实例构造函数。根据C++标准,复制构造函数可以有以下几种形式:

  MyClass( const MyClass& other );
  MyClass( MyClass& other );
  MyClass( volatile const MyClass& other );
  MyClass( volatile MyClass& other );

注意不要写成以下形式,尽管它们也能够实现同样的事:

  MyClass( MyClass* other );
  MyClass( const MyClass* other );
  MyClass( MyClass other );

 

什么时候需要写自己的复制构造函数?

首先,你需要明白假如你没声明一个复制构造函数,编译器会给你提供默认的构造函数。这个默认的构造函数能智能地(member-wise)复制源对象,比如这个类:

class MyClass { int x; char c; std::string s; };

编译器提供的默认构造函数相当以下功能:

 MyClass::MyClass( const MyClass& other ) :
     x( other.x ), c( other.c ), s( other.s )
  {}

在很多情况下,这是足够的。然而,有些情景下member-wise的拷贝方式是不够的。尤为常见的原因是默认的复制构造会产生野指针(raw pointer),这个时候你需要深复制指针成员,这是当你不是想复制指针本身,而是想复制指针指向的内容。为什么需要深复制?这是因为通常一个实例拥有指针,同时在某些时候它也负责删除这个指针,比如析构函数发生的前一刻。假如两个对象都析构调用 delete 函数删除非空指针成员,会导致堆栈溢出。

 

使用编译器提供的默认复制构造函数,很少情况不会出现野指针,因此默认构造函数是不足够的。你也可以使用引用计数。比如boost::shared_ptr<>。

正确使用const  (Const correctness)

当通过引用的方式传递参数给函数或者构造对象时,要很小心地正确使用const。仅仅当函数会修改传递的函数才使用non-canst引用,否则就应该使用const引用。

为什么强调要这样? 这是一个C++标注的小条款:non-const 引用不能绑定到临时对象,临时对象时一个没有变量名的实例,比如:

std::string( "Hello world" );

是一个临时对象,因为没有变量名。下面一个不是临时对象:

std::string s( "Hello world" );

 

这个实用的提醒有什么用?请看下面:

// Improperly declared function:  parameter should be const reference:
  void print_me_bad( std::string& s ) {
      std::cout << s << std::endl;
  }

  // Properly declared function: function has no intent to modify s:
  void print_me_good( const std::string& s ) {
      std::cout << s << std::endl;
  }

  std::string hello( "Hello" );

  print_me_bad( hello );  // Compiles ok; hello is not a temporary
  print_me_bad( std::string( "World" ) );  // Compile error; temporary object
  print_me_bad( "!" ); // Compile error; compiler wants to construct temporary
                       // std::string from const char*

  print_me_good( hello ); // Compiles ok
  print_me_good( std::string( "World" ) ); // Compiles ok
  print_me_good( "!" ); // Compiles ok 

 

很多STL的容器和算法都要求一个对象时可拷贝的。通常,这意味着你需要有实用const传引用的复制构造函数。 (最近一个搞了很久的错误就是这个问题,所以觉得这篇文章很好,就翻译过来分享了)

插曲(译者经历):

我在使用list的时候,用到了replace泛型算法:

template < class ForwardIterator, class T >
  void replace ( ForwardIterator first, ForwardIterator last,
                 const T& old_value, const T& new_value )
{
  for (; first != last; ++first)
    if (*first == old_value) *first=new_value;
}

很明显这个算法要求对象具有 == 操作符来比较。我也为对象重载了 == 操作符:

bool operator==(Person& lhs,Person& rhs)
{
	if (wcscmp(lhs.name,rhs.name)==0)
	{
		return true;
	}
	else
		return false;

}

我这样使用算法:

replace(DataList.begin(),DataList.end(),temp1,temp);
//list<Person> DataList; Person temp,temp1;

尽管我都重载了 == 操作符,但编译器还是报出:

二进制“==”: 没有找到接受“Person”类型的左操作数的运算符(或没有可接受的转换)

这样的错误。

加上const后就可以了 ,也就是:

bool operator==(const Person& lhs,const Person& rhs)

 

问题原因关键在于:replace算法的参数是使用了const引用,而当客户自定义的 == 操作符是non-const引用,就会出现一个没有接受的转换,要知道,const数据类型是不能转换到non-const类型的,相反non-const就可以转换到const类型,正确使用const是很重要的!

 

什么是赋值操作符?

赋值操作符是允许你使用= 给一个实例赋值,比如:

MyClass c1, c2;
  c1 = c2;  // assigns c2 to c1 

赋值操作符有以下几种不同形式:

(1) MyClass& operator=( const MyClass& rhs );
(2) MyClass& operator=( MyClass& rhs );
(3) MyClass& operator=( MyClass rhs );
(4) const MyClass& operator=( const MyClass& rhs );
(5) const MyClass& operator=( MyClass& rhs );
(6) const MyClass& operator=( MyClass rhs );
(7) MyClass operator=( const MyClass& rhs );
(8) MyClass operator=( MyClass& rhs );
(9) MyClass operator=( MyClass rhs );

返回值的类型相对不重要(但译者我觉得还是跟系统内置的=返回值一样较为合适,也就是返回对象的引用)

(2)(5)(8)通过non-caonst传递右值引用,当出现以下情况,就会编译出错:

MyClass c1;
  c1 = MyClass( 5, 'a', "Hello World" );  // assuming this constructor exists

 

什么时候需要些赋值操作符?

首先,你要明白这个跟复制构造函数一样,假如你没声明赋值操作符,编译器同样为你提供一个默认的,同样这个默认的赋值操作符也只是“浅赋值” (member-wise assignment).跟以下功能相当:

MyClass& MyClass::operator=( const MyClass& rhs ) {
      x = other.x;
      c = other.c;
      s = other.s;
      return *this;
  }

同样也是会产生也指针的问题。通常任何时候你需要写自定义的复制构造函数,也就需要些自定义的赋值操作符。

本文固定链接:  http://www.coderess.com/?p=55 | Pro.Charm

你可能感兴趣的:(复制构造,赋值操作符,const重要性)