c++ const 成员函数 & 临时变量 & 右值引用 & move

来源:互联网 发布:自学电脑编程最快多久 编辑:程序博客网 时间:2024/06/10 18:22

const 成员函数

尊重原作,部分转自http://blog.csdn.net/lihao21/article/details/8634876

我们知道,在C++中,若一个变量声明为const类型,则试图修改该变量的值的操作都被视编译错误。例如:

const char blank = 'a';  blank = 'b';  // 错误  

面向对象程序设计中,为了体现封装性,通常不允许直接修改类对象的数据成员。若要修改类对象,应调用公有成员函数来完成。为了保证const对象的常量性,编译器须区分不安全与安全的成员函数(即区分试图修改类对象与不修改类对象的函数),例如:

const Screen blankScreen;  blankScreen.display();   // 对象的读操作  blankScreen.set('*');    // 错误:const类对象不允许修改, but but but...这个不允许被修改并不代表 const 成员函数就一定线程安全, see Effective Modern c++ Item 16 

在C++中,只有被声明为const的成员函数才能被一个const类对象调用。

要声明一个const类型的类成员函数,只需要在成员函数参数列表后加上关键字const,例如:

class Screen {  public:     char get() const;  };

在类体之外定义const成员函数时,还必须加上const关键字,例如:

char Screen::get() const {     return _screen[_cursor];  }  

若将成员成员函数声明为const,则该函数不允许修改类的数据成员。例如:

class Screen {  public:  int ok() const {return _cursor; }  int error(intival) const { _cursor = ival; }  };  

在上面成员函数的定义中,ok()的定义是合法的,error()的定义则非法。
值得注意的是,把一个成员函数声明为const可以保证这个成员函数不修改数据成员,但是,如果据成员是指针(注:bitwise constness和logical constness),则const成员函数并不能保证不修改指针指向的对象,编译器不会把这种修改检测为错误。例如:

class Name {  public:  void setName(const string &s) const;  private:      char *m_sName;  };  void setName(const string &s) const {      m_sName = s.c_str();      // 错误!不能修改m_sName;  for (int i = 0; i < s.size(); ++i)       m_sName[i] = s[i];       // 不好的风格,但不是错误的  }  

虽然m_Name不能被修改,但m_sName是char *类型,const成员函数可以修改其所指向的字符。

const成员函数可以被具有相同参数列表的非const成员函数重载,例如:

class Screen {  public:  char get(int x,int y);  char get(int x,int y) const;  };  

在这种情况下,类对象的常量性决定调用哪个函数。

const Screen cs;  Screen cc2;  char ch = cs.get(0, 0);  // 调用const成员函数  ch = cs2.get(0, 0);     // 调用非const成员函数  

小结:

  • 1)const成员函数可以访问非const对象所有数据,也可以访问const对象内的所有数据成员;
  • 2)非const成员函数可以可以访问非const对象所有数据,但是不可以访问const对象内的任何数据成员;
  • 3)作为一种良好的编程风格,在声明一个成员函数时,若该成员函数并不对数据成员进行修改操作,应尽可能将该成员函数声明为const 成员函数。
  • 4)此外,尽量将不会被修改的函数参数声明为 const, 因为这样的函数也可以将 临时对象 作为参数,否则会编译不通过。

函数临时变量:

Question:
When creating a new instance of a MyClass as an argument to a function like so:

class MyClass{  MyClass(int a);};    myFunction(MyClass(42));

does the standard make any grantees on the timing of the destructor?
Specifically, can I assume that the it is going to be called before the next statement after the call to myFunction() ?

Answer:
Temporary objects are destroyed at the end of the full expression they are part of.

A full expression is an expression that isn’t a sub-expression of some other expression. Usually this means it ends at the ; (or ) for if, while, switch etc.) denoting the end of the statement. In your example, it’s the end of the function call.

Note that you can extend the lifetime of temporaries by binding them to a const reference. Doing so extends their lifetime to the reference’s lifetime:

MyClass getMyClass();{  const MyClass& r = getMyClass(); // full expression ends here  ...} // object returned by getMyClass() is destroyed here

If you don’t plan to change the returned object, then this is a nice trick to safe a copy constructor call (compared to MyClass obj = getMyClass();) which unfortunately isn’t very well known. (I suppose C++11’s move semantics will render it less useful, though.)

see link: http://stackoverflow.com/questions/2506793/c-life-span-of-temporary-arguments

右值引用:

当你函数返回的值是一个 ,而不是一个 引用 的时候,就会出现一个临时对象了,所以大家都很喜欢传递引用(而不是值),以下讨论都是基于传递引用的情况。
但是在c++11以前你只能通过一个 const 引用去绑定一个临时对象(或者叫右值),否则就编译不通过,const 就意味着你不能对该临时对象进行任何修改了。

假设一种情况,可以修改临时对象,例如:在以一个临时对象为参数构造一个新的对象时,假设那个临时对象new了一块内存,我们在新的对象中可以直接复制该临时对象new了内存指针,然后将那个临时对象的指针 修改 为 NULL, 这样该临时对象析构的时候就会delete一个NULL指针——内存还保留着,而实际分配的内存就被转移到了我们这个新的对象中,这样是不是就很有效率了呢?

这里有两个问题: 第一个就是如何判断该对象是临时对象,第二个就是该临时对象不能是const类型的。

c++11中的右值引用(rvalue reference)解决了这两个问题!

// 如果func参数是引用(避免复制),则必须为 const 引用func(std::string("abc"));  // 传入的是个临时对象(rvalue), 该临时对象的生命周期 see abovestd::string str("abc");func(str)  // 传入的不是临时变量(lvalue)

Prior to C++11, if you had a temporary object, you could use a “regular” or “lvalue reference” to bind it, but only if it was const:

const string& name = getName(); // okstring& name = getName(); // NOT ok

The intuition here is that you cannot use a “mutable” reference because, if you did, you’d be able to modify some object that is about to disappear, and that would be dangerous. Notice, by the way, that holding on to a const reference to a temporary object ensures that the temporary object isn’t immediately destructed. This is a nice guarantee of C++, but it is still a temporary object, so you don’t want to modify it.

In C++11, however, there’s a new kind of reference, an “rvalue reference”, that will let you bind a mutable reference to an rvalue, but not an lvalue. In other words, rvalue references are perfect for detecting if a value is temporary object or not. Rvalue references use the && syntax instead of just &, and can be const and non-const, just like lvalue references, although you’ll rarely see a const rvalue reference (as we’ll see, mutable references are kind of the point):

const string&& name = getName(); // okstring&& name = getName(); // also ok - praise be!

你编写的 move 函数能够被成功调用还要有两个条件: 1, 必须传入一个右值 2, 该右值能够被修改(即不是const)
std::move就是保证这两个条件能够满足,(std::move 的作用就是将一个变量强制类型转换为 右值引用 类型)
例如:

std::vector<int> a = {1,2,3,4,5};std::vector<int> b = {3,2,1};b = a;  // 调用operator= 复制各个元素b = std::move(a); // 调用 move operator 移动各个元素,高效!所有的容器元素都是通过 new 出来的,所以move用在这里再好不过了。不过此时 a 就变成了空vector了

如下图(ref. effective modern c++),move 一个 vector 相当于移动一个指针(vw1 变为 null):
这里写图片描述
还有一个注意点: 在多层右值函数调用的时候,每一层函数参数都需要 显示 强制类型转换(用std::move函数)为 右值引用 (因为在函数内部,该参数只是一个引用——是一个左值,不是右值),否则下层函数调用将会重载参数为 const 引用左值 的版本,而不是右值版本。

Put a final way: both lvalue and rvalue references are lvalue expressions. The difference is that an lvalue reference must be const to hold a reference to an rvalue, whereas an rvalue reference can always hold a reference to an rvalue. It’s like the difference between a pointer, and what is pointed to. The thing pointed-to came from an rvalue, but when we use rvalue reference itself, it results in an lvalue.

不过, rvalue references cannot magically keep an object alive for you, 所以这样是不可行的( 根返回lvalue引用是类似的,不能返回临时对象的引用 ):

int && getRvalueInt (){    int x = 123;    return std::move( x );}

总结下来, 左值引用 和 右值引用 一样,都只是个引用(是个右值),不同点是,左值引用只能绑定到左值右值引用可以绑定到右值,并且右值常量引用也可以绑定到左值

所以说,右值引用 和 std::move 对那种包含一大块内存的 临时对象 具有很大的利用率。尤其是对于需要在内部new一些内存的对象(如vector, 很长的 string等等)。对于这些对象,自己要写 move 构造函数 和 move operator(对于自己编写的类,如果实现了copy operations, move operations, or destructors 这里头的任何一个函数,编译器就不会默认自动生成这些函数了,需要自己挨个实现——or 显示写=default)。 而且要考虑自己写的哪些函数会影响编译自动生成的函数(默认构造,默认复制,默认赋值 等等等)。
c++11所有的 stl 容器都已经实现了 move constructor 和 move assignment operator, 在使用标准容器的时候可以可以考虑优化一下代码。

see link: http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html

see link: http://eli.thegreenplace.net/2011/12/15/understanding-lvalues-and-rvalues-in-c-and-c

0 0