原地址:http://www.cplusplus.com/doc/tutorial/classes2/

 

Special members

[NOTE: This chapter requires proper understanding of dynamically allocated memory]

 

Special member functions are member functions that are implicitly defined as member of classes under certain circumstances. There are six:

 

Let's examine each of these:

 

1. Default constructor

 

1-1 

The default constructor is the constructor called when objects of a class are declared, but are not initialized with any arguments.

 

If a class definition has no constructors, the compiler assumes the class to have an implicitly defined default constructor. Therefore, after declaring a class like this:

 

classExample { //the class without constructor, consider                 it has the default constructor

  public:

    inttotal;

    void accumulate (int x) { total += x; }

};

 

The compiler assumes that Example has a default constructor. Therefore, objects of this class can be constructed by simply declaring them without any arguments:

 

 

Example ex; //constructor without any arguments

 

1-2
But as soon as a class has some constructor taking any number of parameters explicitly declared, the compiler no longer provides an implicit default constructor, and no longer allows the declaration of new objects of that class without arguments. For example, the following class:

 

classExample2 {

  public:

    inttotal;

    Example2 (int initial_value) : total(initial_value) { }; //constructor explicit constructor taking one argument

    void accumulate (int x) { total += x; };

};

 

Here, we have declared a constructor with a parameter of type int. Therefore the following object declaration would be correct:

 

 

Example2 ex (100);   // ok: calls constructor

 

 

But the following:

 

Example2 ex;         // not valid: no default constructor

 

 

Would not be valid, since the class has been declared with an explicit constructor taking one argument and that replaces the implicit default constructor taking none.

 

1-3

Therefore, if objects of this class need to be constructed without arguments, the proper default constructor shall also be declared in the class. For example:

 

// classes and default constructors

#include<iostream>

#include<string>

usingnamespace std;

 

classExample3 {

    string data;

  public:

    Example3 (const string& str) : data(str) {}

    Example3() {} // default constructor,constructor without parameters

    const string& content() const {returndata;}

};

 

int main () {

  Example3 foo;

  Example3 bar ("Example");

 

  cout << "bar's content: " << bar.content() << '\n';

  return 0;

}

 

 you should get:

 

bar's content: Example

 

 

Example3() {}; //class Example3 to be constructed without arguments

 

Here, Example3 has a default constructor (i.e., a constructor without parameters) defined as an empty block:

 

 

This allows objects of class Example3 to be constructed without arguments (like foo was declared in this example). Normally, a default constructor like this is implicitly defined for all classes that have no other constructors and thus no explicit definition is required. But in this case, Example3 has another constructor:

 

 

Example3 (const string& str);

 

And when any constructor is explicitly declared in a class, no implicit default constructors is automatically provided.

 

 

Destructor

 

Destructors fulfill the opposite functionality of constructors: They are responsible for the necessary cleanup needed by a class when its lifetime ends. The classes we have defined in previous chapters did not allocate any resource and thus did not really require any clean up.

 

But now, let's imagine that the class in the last example allocates dynamic memory to store the string, it had as data member; in this case, it would be very useful to have a function called automatically at the end of the object's life in charge of releasing this memory. To do this, we use a destructor. A destructor is a member function very similar to a default constructor: it takes no arguments and returns nothing, not even void. It also uses the class name as its own name, but preceded with a tilde sign (~):

 

 

// destructors

#include<iostream>

#include<string>

usingnamespace std;

 

classExample4 {

    string* ptr;

  public:

    // constructors:

    Example4() : ptr(newstring) {}

    Example4 (conststring& str) : ptr(newstring(str)) {}

    // destructor:

    ~Example4 () {deleteptr;}

    // access content:

    conststring& content() const {return *ptr;}

};

 

int main () {

  Example4 foo;

  Example4 bar ("Example");

 

  cout << "bar's content: " << bar.content() << '\n';

  return 0;

}

 

you should get

 

bar's content: Example

 

 

On construction, Example4 allocates storage for a string. Storage that is later released by the destructor.

 

The destructor for an object is called at the end of its lifetime; in the case of foo and bar this happens at the end of function main.

 

 

Copy constructor

 

When an object is passed a named object of its own type as argument, its copy constructor is invoked in order to construct a copy.

 

A copy constructor is a constructor whose first parameter is of type reference to the class itself (possibly const qualified) and which can be invoked with a single argument of this type. For example, for a class MyClass, the copy constructor may have the following signature:

 

 

MyClass::MyClass (const MyClass&);

 

If a class has no custom copy nor move constructors (or assignments) defined, an implicit copy constructor is provided. This copy constructor simply performs a copy of its own members. For example, for a class such as:

 

classMyClass {

  public:

    inta, b; string c;

};

 

 

An implicit copy constructor is automatically defined. The definition assumed for this function performs a shallow copy, roughly equivalent to:

 

 

MyClass::MyClass(const MyClass& x) : a(x.a), b(x.b), c(x.c) {}

 

 

This default copy constructor may suit the needs of many classes. But shallow copies only copy the members of the class themselves, and this is probably not what we expect for classes like class Example4 we defined above, because it contains pointers of which it handles its storage. For that class, performing a shallow copy means that the pointer value is copied, but not the content itself; This means that both objects (the copy and the original) would be sharing a single string object (they would both be pointing to the same object), and at some point (on destruction) both objects would try to delete the same block of memory, probably causing the program to crash on runtime. This can be solved by defining the following custom copy constructor that performs a deep copy:

 

// copy constructor: deep copy

#include<iostream>

#include<string>

usingnamespace std;

 

classExample5 {

    string* ptr;

  public:

    Example5 (conststring& str) : ptr(newstring(str)) {}

    ~Example5 () {deleteptr;}

    // copy constructor:

    Example5 (constExample5& x) : ptr(newstring(x.content())) {}

    // access content:

    conststring& content() const {return *ptr;}

};

 

int main () {

  Example5 foo ("Example");

  Example5 bar = foo;

 

  cout << "bar's content: " << bar.content() << '\n';

  return 0;

}

 

 

bar's content: Example

 

 

The deep copy performed by this copy constructor allocates storage for a new string, which is initialized to contain a copy of the original object. In this way,both objects (copy and original) have distinct copies of the content stored in different locations.

 

Copy assignment

 

Objects are not only copied on construction, when they are initialized: They can also be copied on any assignment operation. See the difference:

 

 

MyClass foo;

MyClass bar (foo);       // object initialization: copy constructor called

MyClass baz = foo;       // object initialization: copy constructor called

foo = bar;               // object already initialized: copy assignment called 

 

 

Note that bar is initialized on construction using an equal sign, but this is not an assignment operation! (although it may look like one): The declaration of an object is not an assignment operation, it is just another of the syntaxes to call single-argument constructors.

 

The assignment on foo is an assignment operation. No object is being declared here, but an operation is being performed on an existing object; foo.

 

The copy assignment operator is an overload of operator= which takes a value or reference of the class itself as parameter. The return value is generally a reference to *this (although this is not required). For example, for a class MyClass, the copy assignment may have the following signature:

 

 

MyClass& operator= (const MyClass&);

 

 

The copy assignment operator is also a special function and is also defined implicitly if a class has no custom copy nor move assignments (nor move constructor) defined.

 

But again, the implicit version performs a shallow copy which is suitable for many classes, but not for classes with pointers to objects they handle its storage, as is the case in Example5. In this case, not only the class incurs the risk of deleting the pointed object twice, but the assignment creates memory leaks by not deleting the object pointed by the object before the assignment. These issues could be solved with a copy assignment that deletes the previous object and performs a deep copy:

 

Example5& operator= (const Example5& x) {

  delete ptr;                      // delete currently pointed string

  ptr = new string (x.content());  // allocate space for new string, and copy

  return *this;

}

 

 

Or even better, since its string member is not constant, it could re-utilize the same string object:

 

Example5& operator= (const Example5& x) {

  *ptr = x.content();

  return *this;

}

 

 

 

Move constructor and assignment

 

Similar to copying, moving also uses the value of an object to set the value to another object. But, unlike copying, the content is actually transferred from one object (the source) to the other (the destination): the source loses that content, which is taken over by the destination. This moving only happens when the source of the value is an unnamed object. 

 

Unnamed objects are objects that are temporary in nature, and thus haven't even been given a name. Typical examples of unnamed objects are return values of functions or type-casts.

 

Using the value of a temporary object such as these to initialize another object or to assign its value, does not really require a copy: the object is never going to be used for anything else, and thus, its value can be moved into the destination object. These cases trigger the move constructor and move assignments:

 

The move constructor is called when an object is initialized on construction using an unnamed temporary. Likewise, the move assignment is called when an object is assigned the value of an unnamed temporary:

 

 

MyClass fn();            // function returning a MyClass object

 

MyClass foo;             // default constructor

MyClass bar = foo;       // copy constructor

MyClass baz = fn();      // move constructor

 

foo = bar;               // copy assignment

baz = MyClass();         // move assignment

 

 

Both the value returned by fn and the value constructed with MyClass are unnamed temporaries. In these cases, there is no need to make a copy, because the unnamed object is very short-lived and can be acquired by the other object when this is a more efficient operation.

 

The move constructor and move assignment are members that take a parameter of type rvalue reference to the class itself:

 

MyClass (MyClass&&);             // move-constructor

MyClass& operator= (MyClass&&);  // move-assignment

 

 

An rvalue reference is specified by following the type with two ampersands (&&). As a parameter, an rvalue reference matches arguments of temporaries of this type.

 

The concept of moving is most useful for objects that manage the storage they use, such as objects that allocate storage with new and delete. In such objects, copying and moving are really different operations:

 

Copying from A to B means that new memory is allocated to B and then the entire content of A is copied to this new memory allocated for B.

 

- Moving from A to B means that the memory already allocated to A is transferred to B without allocating any new storage. It involves simply copying the pointer.

 

For example:

 

// move constructor/assignment

#include<iostream>

#include<string>

usingnamespace std;

 

classExample6 {

    string* ptr;

  public:

    Example6 (conststring& str) : ptr(newstring(str)) {}

    ~Example6 () {deleteptr;}

    // move constructor

    Example6 (Example6&& x) : ptr(x.ptr) {x.ptr=nullptr;}

    // move assignment

    Example6& operator= (Example6&& x) {

      deleteptr; 

      ptr = x.ptr;

      x.ptr=nullptr;

      return *this;

    }

    // access content:

    conststring& content() const {return *ptr;}

    // addition:

    Example6 operator+(constExample6& rhs) {

      returnExample6(content()+rhs.content());

    }

};

 

 

int main () {

  Example6 foo ("Exam");

  Example6 bar = Example6("ple");   // move-construction

  

  foo = foo + bar;                  // move-assignment

 

  cout << "foo's content: " << foo.content() << '\n';

  return 0;

}

 

you should get

 

foo's content: Example

 

 

Compilers already optimize many cases that formally require a move-construction call in what is known as Return Value Optimization. Most notably, when the value returned by a function is used to initialize an object. In these cases, the move constructor may actually never get called.

 

Note that even though rvalue references can be used for the type of any function parameter, it is seldom useful for uses other than the move constructor. Rvalue references are tricky, and unnecessary uses may be the source of errors quite difficult to track.

 

 

Implicit members

 

The six special members functions described above are members implicitly declared on classes under certain circumstances:

 

 

Notice how not all special member functions are implicitly defined in the same cases. This is mostly due to backwards compatibility with C structures and earlier C++ versions, and in fact some include deprecated cases. Fortunately, each class can select explicitly which of these members exist with their default definition or which are deleted by using the keywords default and delete, respectively. The syntax is either one of:

 

 

function_declaration = default;

function_declaration = delete;

 

For example:

 

// default and delete implicit members

#include <iostream>

using namespace std;

 

class Rectangle {

    int width, height;

  public:

    Rectangle (int x, int y) : width(x), height(y) {}

    Rectangle() = default;

    Rectangle (const Rectangle& other) = delete;

    int area() {return width*height;}

};

 

int main () {

  Rectangle foo;

  Rectangle bar (10,20);

 

  cout << "bar's area: " << bar.area() << '\n';

  return 0;

}

bar's area: 200

 

 

Here, Rectangle can be constructed either with two int arguments or be default-constructed (with no arguments). It cannot however be copy-constructed from another Rectangle object, because this function has been deleted. Therefore, assuming the objects of the last example, the following statement would not be valid:

 

 

Rectangle baz (foo);

 

 

It could, however, be made explicitly valid by defining its copy constructor as:

 

 

Rectangle::Rectangle (const Rectangle& other) = default;

 

 

Which would be essentially equivalent to:

 

 

Rectangle::Rectangle (const Rectangle& other) : width(other.width), height(other.height) {}

 

 

Note that, the keyword default does not define a member function equal to the default constructor (i.e., where default constructor means constructor with no parameters), but equal to the constructor that would be implicitly defined if not deleted.

 

In general, and for future compatibility, classes that explicitly define one copy/move constructor or one copy/move assignment but not both, are encouraged to specify either delete or default on the other special member functions they don't explicitly define.

(转) Special members的更多相关文章

  1. C++ Core Guidelines

    C++ Core Guidelines September 9, 2015 Editors: Bjarne Stroustrup Herb Sutter This document is a very ...

  2. C++中的Trivial 、POD、non-POD和Standard Layout概念

    POD types non-POD types Standard Layout types A Formal Definition Informally, a standard layout clas ...

  3. AutoMapper:Unmapped members were found. Review the types and members below. Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type

    异常处理汇总-后端系列 http://www.cnblogs.com/dunitian/p/4523006.html 应用场景:ViewModel==>Mode映射的时候出错 AutoMappe ...

  4. HDU 4569 Special equations(取模)

    Special equations Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u S ...

  5. (转载)phpcms v9两步实现专题栏目生成路径去掉html和special

    相信很多人都知道,phpcms v9专题是不支持自定义URL的,生成的专题路径是以/HTML/special/开头的.那么如何实现专题栏目生成路径去掉html和special呢?通过修改程序的PHP源 ...

  6. [XAF] How to define a business class at runtime or allow end-users to configure its members via the application UI?

    How to define a business class at runtime or allow end-users to configure its members via the applic ...

  7. JavaScript Patterns 5.6 Static Members

    Public Static Members // constructor var Gadget = function (price) { this.price = price; }; // a sta ...

  8. QIBO CMS SQL Injection Via Variable Uninitialization In \member\special.php

    Catalog . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述 该漏洞存在于/member/special.php文件下,由于未对变量进 ...

  9. Jquery报错:Uncaught TypeError: ((m.event.special[e.origType] || (intermediate value)).handle || e.handler).apply is not a function

    页面中出现了Jquery报错:Uncaught TypeError: ((m.event.special[e.origType] || (intermediate value)).handle || ...

随机推荐

  1. FileUpload

    一upload原理: 1.表单的method必须是post方法 2.enctype属性必须是“mutipatr/form-data”类型 enctype默认的属性是“application/x-www ...

  2. Linux设置自启动

    启动大致过程:bootloader-->内核-->内核模块-->挂载根文件系统-->init进程 init进程是非内核进程中第一个被启动运行的,因此它的进程编号PID的值总是1 ...

  3. tiny xml 使用总结

    这几天在埋头写自己的3D文件浏览器(稍后发布),突发奇想的要把自己的内部格式转化成XML,于是,把以前在研究所时用过的ExPat翻了出来.ExPat是基于事件的XML解释器,速度挺快的,但结构方面有点 ...

  4. 浅析 JavaScript 组件编写

    之前因项目需要也编写过一些简单的JS组件,大多是基于JQuery库的,一直也没有过总结,导致再次写到的时候还去Google, 近日看到一个文章总结的挺好,拿过整理一下做个备忘. 此次同样是基于jque ...

  5. Java笔记--Java的List、Iterator用法

    1. List的使用 准备知识: List继承自Collection接口.List是一种有序集合,List中的元素可以根据索引(顺序号:元素在集合中处于的位置信息)进行取得/删除/插入操作. 跟Set ...

  6. soapUI参数中文乱码问题解决方法 (groovy脚本中文乱码)

    soapUI参数中文乱码问题解决方法 可能方案1: 字体不支持中文,将字体修改即可: file-preferences-editor settings-select font 修改字体,改成能显示中文 ...

  7. android:layout_gravity="bottom"不起作用问题

    布局layout时, 发现设置了android:layout_gravity="bottom"后view并没有底对齐, 查了下, 原来如下: 对于 LinearLayout 当 a ...

  8. position 为absolute时/float 为right,span为block

    元素分为内联元素和区块元素两类(当然也有其它的),在内联元素中有个非常重要的常识,即内两元素是不可以设置区块元素所具有的样式,例如:width | height.relative : 原来是什么类型的 ...

  9. SALT-API兼HALITE测试搞定

    妈XX,真的搞了近一周的空闲时间. 最后才领悟. 其实,先按HALITE的套路弄好,然后直接SALT-API就OK了..因为HALITE就是SALT-API的封闭和替代嘛. 随便参考一个URL搞定HA ...

  10. LeetCode_Palindrome Partitioning II

    Given a string s, partition s such that every substring of the partition is a palindrome. Return the ...