国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入理解c++特殊成員函數(shù)

這篇具有很好參考價(jià)值的文章主要介紹了深入理解c++特殊成員函數(shù)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

深入理解c++特殊成員函數(shù)

在c++中,特殊成員函數(shù)有下面6個(gè):

  • 構(gòu)造函數(shù)
  • 析構(gòu)函數(shù)
  • 復(fù)制構(gòu)造函數(shù)(拷貝構(gòu)造函數(shù))
  • 賦值運(yùn)算符(拷貝運(yùn)算符)
  • 移動(dòng)構(gòu)造函數(shù)(c++11引入)
  • 移動(dòng)運(yùn)算符(c++11引入)

以Widget類為例,其特殊成員函數(shù)的簽名如下所示:

class Widget{
public:
    Widget();//構(gòu)造函數(shù)
    ~Widget();//析構(gòu)函數(shù)
    Widget(const Widget& rhs);//復(fù)制構(gòu)造函數(shù)(拷貝構(gòu)造函數(shù))
    Widget& operator=(const Widget& rhs);//賦值運(yùn)算符(拷貝運(yùn)算符)
    Widget(Widget&& rhs);//移動(dòng)構(gòu)造函數(shù)
    Widget& operator=(Widget&& rhs);//移動(dòng)運(yùn)算符
}

每個(gè)方法都有哪些作用,又都有哪些注意點(diǎn)?

本文將針對(duì)這些方法,進(jìn)行詳細(xì)的講解。

構(gòu)造函數(shù)

構(gòu)造函數(shù)的作用是幫助創(chuàng)建對(duì)象的實(shí)例,并對(duì)實(shí)例進(jìn)行初始化。

在c++中,下面兩種形式的語句將會(huì)調(diào)用類的構(gòu)造函數(shù):

Widget widget;
Widget *w = new Widget();

調(diào)用構(gòu)造函數(shù)將會(huì)創(chuàng)建一個(gè)類的實(shí)例對(duì)象。當(dāng)一個(gè)類擁有數(shù)據(jù)成員時(shí),就需要為該類編寫構(gòu)造函數(shù),在構(gòu)造函數(shù)中對(duì)數(shù)據(jù)成員進(jìn)行初始化。

對(duì)于c++98,如果一個(gè)類沒有set方法,那么就需要為其創(chuàng)建含參數(shù)的構(gòu)造函數(shù),如下所示:

#include <iostream>

class Widget
{
public:
    Widget(int width, int height):height_(height), width_(width){}
private:
    int height_;
    int width_;
};

int main()
{
    Widget w(1,1);
    return 0;
}

倘若此時(shí)不為其創(chuàng)建含參數(shù)的構(gòu)造函數(shù),那么此時(shí)創(chuàng)建的對(duì)象中的成員的值是隨機(jī)的,顯而易見,這樣的創(chuàng)建出的對(duì)象是不好的。

#include<iostream>
using namespace std;

class Widget
{
public:
    int getHeight() const{
        return height_;
    }

    int getWidth() const{
        return width_;
    }
private:
    int height_;
    int width_;
};

int main()
{
    Widget w;
    std::cout << w.getHeight()<<std::endl;
    std::cout << w.getWidth()<<std::endl;   
    return 0;
}

但是對(duì)于c++11之后的標(biāo)準(zhǔn),成員的初始值可以在類中定義。

在這種場(chǎng)景下,所有該類創(chuàng)建出的對(duì)象將擁有相同的初始值。如果你希望創(chuàng)建出的對(duì)象的初始值可以是不相同的,那么還是需要添加含參數(shù)的構(gòu)造函數(shù)。

#include<iostream>
using namespace std;

class Widget
{
public:
    int getHeight() const{
        return height_;
    }

    int getWidth() const{
        return width_;
    }
private:
    int height_{1};
    int width_{1};
};

int main()
{
    Widget w;
    std::cout << w.getHeight()<<std::endl;
    std::cout << w.getWidth()<<std::endl;   
    return 0;
}

析構(gòu)函數(shù)

構(gòu)造函數(shù)的作用是幫助銷毀一個(gè)實(shí)例

這很好理解,但是何時(shí)需要自定義析構(gòu)函數(shù)呢?

首先看下面這個(gè)類,這個(gè)類需要寫自定義析構(gòu)函數(shù)嗎?

class Student{
public:
    Student(std::string name , int age, int id):name_(name), age_(age), id(id_){}//需要析構(gòu)函數(shù)嗎?
public:
    std::string getName() const{
        return name_;
    }
    int getAge() const{
        return age_;
    }
    int getId() const{
        return id_;
    }
private:
    std::string name_;
    int age_;
    int id_;
}

答案是否定的,這個(gè)Student類只包含了三個(gè)成員,默認(rèn)的析構(gòu)函數(shù)會(huì)清理掉這些數(shù)據(jù),因此不需要自定義析構(gòu)函數(shù)。

再看看下面這個(gè)例子,需要為其自定義析構(gòu)函數(shù)嗎?

class Student{
public:
    Student(const char* s , std::size_t n)name_(new char[n]);{
        memcpy(name_, s, n);
    }
    //需要析構(gòu)函數(shù)嗎?
public:
    char* getName() const{
        return name_;
    }

private:
    char* name_{nullptr};
}

很顯然,該類需要自定義析構(gòu)函數(shù)。默認(rèn)的析構(gòu)函數(shù)只會(huì)將name_置為nullptr,而不會(huì)釋放new所創(chuàng)建的內(nèi)存空間。

因此上面的例子需要改造為下面這樣的形式:

class Student{
public:
    Student(const char* s , std::size_t n)name_(new char[n]);{
        memcpy(name_, s, n);
    }
    ~Student(){
        if(name_){
            delete[] name_;
        }
    }
    //需要析構(gòu)函數(shù)嗎?
public:
    char* getName() const{
        return name_;
    }

private:
    char* name_{nullptr};
}

其實(shí)這個(gè)類到目前為止還是有問題的,在下文中提到拷貝操作時(shí)會(huì)解釋為什么。

再看看下面這個(gè)例子,需要為其自定義析構(gòu)函數(shù)嗎?

class AsyncExec{
public:
    void exec(std::function<void()>& func){
        threadPtr_ = new std::thread(func);
    }
    //需要析構(gòu)函數(shù)嗎?
private:
    std::thread* threadPtr_{nullptr};
}

很顯然,該類也需要自定義析構(gòu)函數(shù)。AsyncExec類的實(shí)例在調(diào)用完Exec方法后,其內(nèi)部包含了一個(gè)指針,并且其成員是std::thread類型的指針,如果其沒有被detach,那么就必須要進(jìn)行join,否則將會(huì)terminate程序。

因此上面的例子需要改造為下面這樣的形式:

class AsyncExec{
public
    ~AsyncExec(){
        if(threadPtr){
            threadPtr->join;
        }
        delete threadPtr;
    }
public:
    void exec(std::function<void()>& func){
        threadPtr_ = new std::thread(func);
    }
    //需要析構(gòu)函數(shù)嗎?
private:
    std::thread* threadPtr_{nullptr};
}

通過上面兩個(gè)例子,也基本可以發(fā)現(xiàn)這樣的規(guī)律:

通常一個(gè)類需要管理一些資源時(shí)(原始指針,線程,文件描述符等),通常需要為其編寫自定義的析構(gòu)函數(shù),因?yàn)榇藭r(shí)的默認(rèn)的析構(gòu)函數(shù)的行為是不正確的。

接下來需要了解一個(gè)著名的rule of three定理,如果一個(gè)類需要用戶自定義的析構(gòu)函數(shù)用戶定義的復(fù)制構(gòu)造函數(shù)用戶定義的賦值運(yùn)算符三者中的一個(gè),那么它幾乎肯定需要這三個(gè)函數(shù)。

例如下面的例子

#include <cstdint>
#include <cstring>

class Student{
public:
    Student(const char* s , std::size_t n) :name_(new char[n]){
        memcpy(name_, s, n);
    }
    explicit Student(const char* s = "")
        : Student(s, std::strlen(s) + 1) {}
    ~Student(){
        if(name_){
            delete[] name_;
        }
    }
public:
    char* getName() const{
        return name_;
    }

private:
    char* name_;
};

int main()
{
    Student s1("shirley");
    Student s2("tom");

    Student s3(s1);//(1)
    s2 = s1;//(2)
}

如果使用默認(rèn)的復(fù)制構(gòu)造函數(shù),將會(huì)出現(xiàn)double free的錯(cuò)誤。因?yàn)槟J(rèn)的復(fù)制構(gòu)造函數(shù)是值拷貝,此時(shí)s1和s3的name_成員指向同一處內(nèi)存,s1和s3析構(gòu)時(shí)將重復(fù)析構(gòu)。

如果使用默認(rèn)的賦值運(yùn)算符,不僅會(huì)有double free的問題,還會(huì)有一處內(nèi)存泄漏。由于s2被賦值為了s1,因此s2原來的name_指向的內(nèi)存將不再有指針指向,于是產(chǎn)生了內(nèi)存泄漏。接下來,同理s1和s2的name_成員指向同一處內(nèi)存,s1和s2析構(gòu)時(shí)將重復(fù)析構(gòu)。

正確的寫法就是在添加自定義析構(gòu)函數(shù)的同時(shí),為其添加自定義的賦值構(gòu)造函數(shù)和自定義的賦值運(yùn)算符。

#include <cstdint>
#include <cstring>

class Student{
public:
    Student(const char* s , std::size_t n) :name_(new char[n]){
        memcpy(name_, s, n);
    }
    explicit Student(const char* s = "")
        : Student(s, std::strlen(s) + 1) {}
    ~Student(){
        if(name_){
            delete[] name_;
        }
    }
    Student(const Student& other) // II. copy constructor
        : Student(other.name_) {}
 
    Student& operator=(const Student& other) // III. copy assignment
    {
        if (this == &other)
            return *this;
 
        std::size_t n{std::strlen(other.name_) + 1};
        char* new_cstring = new char[n];            // allocate
        std::memcpy(new_cstring, other.name_, n); // populate
        delete[] name_;                           // deallocate
        name_ = new_cstring;
 
        return *this;
    }
public:
    char* getName() const{
        return name_;
    }

private:
    char* name_;
};

int main()
{
    Student s1("shirley");
    Student s2("tom");

    Student s3(s1);
    s2 = s1;
}

賦值運(yùn)算符中的這段代碼的寫法,在effective c++中有提到,這樣做是為了保證異常安全性,這樣的寫法可以確保new的失敗的情況下,不會(huì)對(duì)原有對(duì)象的數(shù)據(jù)進(jìn)行破壞。

    std::size_t n{std::strlen(other.name_) + 1};
    char* new_cstring = new char[n];            // allocate
    std::memcpy(new_cstring, other.name_, n); // populate
    delete[] name_;                           // deallocate
    name_ = new_cstring;

復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符

復(fù)制構(gòu)造函數(shù)的作用是使用一個(gè)已經(jīng)存在的對(duì)象去創(chuàng)建一個(gè)新的對(duì)象。

賦值運(yùn)算符的作用是將一個(gè)對(duì)象的所有成員變量賦值給另一個(gè)已經(jīng)創(chuàng)建的對(duì)象。

二者的區(qū)別在于一個(gè)是創(chuàng)建一個(gè)新對(duì)象,一個(gè)是賦值給一個(gè)已經(jīng)存在的對(duì)象。

在下面的例子中,語法(1)就是調(diào)用復(fù)制構(gòu)造函數(shù), 語法(2)就是調(diào)用賦值運(yùn)算符。

{
    Student s1("shirley");
    Student s2("tom");

    Student s3(s1);//(1)復(fù)制構(gòu)造函數(shù)
    s2 = s1;//(2)賦值運(yùn)算符
}

下面我們回顧下面提到的Student類,看下正確的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符的編寫需要注意什么。

復(fù)制構(gòu)造函數(shù)的功能相對(duì)簡(jiǎn)單,主要是成員的復(fù)制,如果存在類管理的指針,則需要進(jìn)行深拷貝。

Student(const Student& other) // II. copy constructor
    : Student(other.name_) {}

賦值運(yùn)算符的編寫的注意點(diǎn)相對(duì)較多。

  • 首先要添加自我賦值判斷。
  • 其次由于賦值運(yùn)算符是對(duì)一個(gè)已經(jīng)存在的對(duì)象再次賦值,因此首先需要銷毀原有對(duì)象的成員。
  • 接著需要處理成員對(duì)象的賦值,如果存在類管理的指針,則需要進(jìn)行深拷貝。
  • 最后需要將*this進(jìn)行返回,以便進(jìn)行連續(xù)賦值。
Student& operator=(const Student& other) // III. copy assignment
{
    if (this == &other)
        return *this;

    std::size_t n{std::strlen(other.name_) + 1};
    char* new_cstring = new char[n];            // allocate
    std::memcpy(new_cstring, other.name_, n); // populate
    delete[] name_;                           // deallocate
    name_ = new_cstring;

    return *this;
}

當(dāng)你沒有提供自定義的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符時(shí),編譯器將創(chuàng)建默認(rèn)的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符,其將對(duì)成員進(jìn)行淺拷貝。

如果你的類沒有管理資源,那么淺拷貝可能是合適的。如果你的類是管理某些資源的(原始指針線程對(duì)象,文件描述符等),那么大概率默認(rèn)的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符是不合適的。

但是要注意有時(shí)候,類成員雖然包含原始指針,但是并不代表該原始指針由該類管理。

例如下面的例子中,Client類中擁有handler_指針,但是該指針的生命周期并不由該類管理,該類僅僅是使用該指針,因此在這種場(chǎng)景下,淺拷貝就沒有問題,默認(rèn)的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符就可以滿足要求。

#include <memory>
#include <functional>
#include <iostream>
#include <thread>
#include <future>

class IHandler
{
public:
    IHandler() = default;
    virtual ~IHandler() = default;
public:
    virtual void connect() = 0;
};

class TcpHandler  :public IHandler
{
public:
    TcpHandler() = default;
    virtual ~TcpHandler() = default;
public:
    void connect(){
        std::cout << "tcp connect" << std::endl;
    }
};

class UdpHandler : public IHandler
{
public:
    UdpHandler() = default;
    virtual ~UdpHandler() = default;
public:
    void connect() {
        std::cout << "udp connect" << std::endl;
    }
};

class Client{
public:
    Client(IHandler* handler):handler_(handler){};
    ~Client() = default;
public:
    void connect(){
        handler_->connect();
    }
private:
    IHandler* handler_{nullptr};
};

void process(IHandler* handler)
{
    if(!handler) return;

    Client client(handler);
    client.connect();
}
int main()
{
    IHandler* handler = new TcpHandler();
    process(handler);
    delete handler;
    handler = nullptr;
    handler = new UdpHandler();
    process(handler);   
    delete handler;
    handler = nullptr;
}

因此,在設(shè)計(jì)類的時(shí)候,需要注意類是否是管理資源還是僅僅是使用資源。如果是管理資源,那么大概率你需要自定義復(fù)制構(gòu)造函數(shù)賦值運(yùn)算符

這里再次會(huì)提到rule of three定理,通常情況下,如果你需要自定義析構(gòu)函數(shù)的時(shí)候,大概率你就需要自定義復(fù)制構(gòu)造函數(shù)賦值運(yùn)算符

牢記這個(gè)點(diǎn),當(dāng)你在設(shè)計(jì)一個(gè)類時(shí)需要有這樣的條件反射。

其實(shí)如果當(dāng)你自定義了析構(gòu)函數(shù)之后,默認(rèn)的復(fù)制構(gòu)造函數(shù)賦值運(yùn)算符就可以被delete,但是在c++98年代,這個(gè)點(diǎn)還沒有被重視。到了c++11年代,因?yàn)榭紤]到舊代碼的遷移困難,這個(gè)點(diǎn)還是沒有繼續(xù)支持。編譯器選擇對(duì)新支持的移動(dòng)構(gòu)造函數(shù)和移動(dòng)運(yùn)算符支持這個(gè)點(diǎn)上的考慮,即如果定義了析構(gòu)函數(shù),則默認(rèn)的移動(dòng)構(gòu)造函數(shù)和移動(dòng)運(yùn)算符將會(huì)delete,這個(gè)點(diǎn)在下面還會(huì)繼續(xù)講解。

移動(dòng)構(gòu)造函數(shù)和移動(dòng)運(yùn)算符

移動(dòng)語義在c++11之后大面積使用,它允許將一個(gè)對(duì)象的所有權(quán)從一個(gè)對(duì)象轉(zhuǎn)移到另一個(gè)對(duì)象,而不需要進(jìn)行數(shù)據(jù)的拷貝。 這種轉(zhuǎn)移可以在對(duì)象生命周期的任意時(shí)刻進(jìn)行,可以說是一種輕量級(jí)的復(fù)制操作。

而移動(dòng)構(gòu)造函數(shù)和移動(dòng)運(yùn)算符就是在類中支持移動(dòng)語義的二個(gè)方法。

關(guān)于如何書寫移動(dòng)構(gòu)造函數(shù)和移動(dòng)運(yùn)算符,這里參考微軟的文檔進(jìn)行理解。

移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符

下面的例子是用于管理內(nèi)存緩沖區(qū)的 C++ 類 MemoryBlock。

// MemoryBlock.h
#pragma once
#include <iostream>
#include <algorithm>

class MemoryBlock
{
public:

   // Simple constructor that initializes the resource.
   explicit MemoryBlock(size_t length)
      : _length(length)
      , _data(new int[length])
   {
      std::cout << "In MemoryBlock(size_t). length = "
                << _length << "." << std::endl;
   }

   // Destructor.
   ~MemoryBlock()
   {
      std::cout << "In ~MemoryBlock(). length = "
                << _length << ".";

      if (_data != nullptr)
      {
         std::cout << " Deleting resource.";
         // Delete the resource.
         delete[] _data;
      }

      std::cout << std::endl;
   }

   // Copy constructor.
   MemoryBlock(const MemoryBlock& other)
      : _length(other._length)
      , _data(new int[other._length])
   {
      std::cout << "In MemoryBlock(const MemoryBlock&). length = "
                << other._length << ". Copying resource." << std::endl;

      std::copy(other._data, other._data + _length, _data);
   }

   // Copy assignment operator.
   MemoryBlock& operator=(const MemoryBlock& other)
   {
      std::cout << "In operator=(const MemoryBlock&). length = "
                << other._length << ". Copying resource." << std::endl;

      if (this != &other)
      {
         // Free the existing resource.
         delete[] _data;

         _length = other._length;
         _data = new int[_length];
         std::copy(other._data, other._data + _length, _data);
      }
      return *this;
   }

   // Retrieves the length of the data resource.
   size_t Length() const
   {
      return _length;
   }

private:
   size_t _length; // The length of the resource.
   int* _data; // The resource.
};

為MemoryBlock創(chuàng)建移動(dòng)構(gòu)造函數(shù)

  • 1.定義一個(gè)空的構(gòu)造函數(shù)方法,該方法采用一個(gè)對(duì)類類型的右值引用作為參數(shù),如以下示例所示:
MemoryBlock(MemoryBlock&& other)
   : _data(nullptr)
   , _length(0)
{
}
  • 2.在移動(dòng)構(gòu)造函數(shù)中,將源對(duì)象中的類數(shù)據(jù)成員添加到要構(gòu)造的對(duì)象:
_data = other._data;
_length = other._length;
  • 3.將源對(duì)象的數(shù)據(jù)成員分配給默認(rèn)值。 這可以防止析構(gòu)函數(shù)多次釋放資源(如內(nèi)存):
other._data = nullptr;
other._length = 0;

為MemoryBloc類創(chuàng)建移動(dòng)賦值運(yùn)算符

  • 1.定義一個(gè)空的賦值運(yùn)算符,該運(yùn)算符采用一個(gè)對(duì)類類型的右值引用作為參數(shù)并返回一個(gè)對(duì)類類型的引用,如以下示例所示:
MemoryBlock& operator=(MemoryBlock&& other)
{
}
  • 2.在移動(dòng)賦值運(yùn)算符中,如果嘗試將對(duì)象賦給自身,則添加不執(zhí)行運(yùn)算的條件語句。
if (this != &other)
{
}
  • 3.在條件語句中,從要將其賦值的對(duì)象中釋放所有資源(如內(nèi)存)。

以下示例從要將其賦值的對(duì)象中釋放 _data 成員:

// Free the existing resource.
delete[] _data;
  • 4.執(zhí)行第一個(gè)過程中的步驟 2 和步驟 3 以將數(shù)據(jù)成員從源對(duì)象轉(zhuǎn)移到要構(gòu)造的對(duì)象:
// Copy the data pointer and its length from the
// source object.
_data = other._data;
_length = other._length;

// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other._data = nullptr;
other._length = 0;
  • 5.返回對(duì)當(dāng)前對(duì)象的引用,如以下示例所示:
return *this;

完整的MemoryBlock類如下所示:

#include <iostream>
#include <algorithm>

class MemoryBlock
{
public:

   // Simple constructor that initializes the resource.
   explicit MemoryBlock(size_t length)
      : _length(length)
      , _data(new int[length])
   {
      std::cout << "In MemoryBlock(size_t). length = "
                << _length << "." << std::endl;
   }

   // Destructor.
   ~MemoryBlock()
   {
      std::cout << "In ~MemoryBlock(). length = "
                << _length << ".";

      if (_data != nullptr)
      {
         std::cout << " Deleting resource.";
         // Delete the resource.
         delete[] _data;
      }

      std::cout << std::endl;
   }

   // Copy constructor.
   MemoryBlock(const MemoryBlock& other)
      : _length(other._length)
      , _data(new int[other._length])
   {
      std::cout << "In MemoryBlock(const MemoryBlock&). length = "
                << other._length << ". Copying resource." << std::endl;

      std::copy(other._data, other._data + _length, _data);
   }

   // Copy assignment operator.
   MemoryBlock& operator=(const MemoryBlock& other)
   {
      std::cout << "In operator=(const MemoryBlock&). length = "
                << other._length << ". Copying resource." << std::endl;

      if (this != &other)
      {
          // Free the existing resource.
          delete[] _data;

          _length = other._length;
          _data = new int[_length];
          std::copy(other._data, other._data + _length, _data);
      }
      return *this;
   }
    // Move constructor.
    MemoryBlock(MemoryBlock&& other) noexcept
    : _data(nullptr)
    , _length(0)
    {
        std::cout << "In MemoryBlock(MemoryBlock&&). length = "
                    << other._length << ". Moving resource." << std::endl;

        // Copy the data pointer and its length from the
        // source object.
        _data = other._data;
        _length = other._length;

        // Release the data pointer from the source object so that
        // the destructor does not free the memory multiple times.
        other._data = nullptr;
        other._length = 0;
    }

    // Move assignment operator.
    MemoryBlock& operator=(MemoryBlock&& other) noexcept
    {
        std::cout << "In operator=(MemoryBlock&&). length = "
                    << other._length << "." << std::endl;

        if (this != &other)
        {
            // Free the existing resource.
            delete[] _data;

            // Copy the data pointer and its length from the
            // source object.
            _data = other._data;
            _length = other._length;

            // Release the data pointer from the source object so that
            // the destructor does not free the memory multiple times.
            other._data = nullptr;
            other._length = 0;
        }
        return *this;
    }
   // Retrieves the length of the data resource.
   size_t Length() const
   {
        return _length;
   }

private:
   size_t _length; // The length of the resource.
   int* _data; // The resource.
};

值得一提的是,有時(shí)候?yàn)榱藴p少重復(fù)代碼,在移動(dòng)構(gòu)造函數(shù)中也可以調(diào)用移動(dòng)運(yùn)算符,不過需要確保這樣做不會(huì)有什么問題。

// Move constructor.
MemoryBlock(MemoryBlock&& other) noexcept
   : _data(nullptr)
   , _length(0)
{
    *this = std::move(other);
}

下面要介紹的是,如果一個(gè)類自定了析構(gòu)函數(shù),賦值構(gòu)造函數(shù),賦值運(yùn)算符三者之一,則默認(rèn)的移動(dòng)構(gòu)造和移動(dòng)運(yùn)算符就會(huì)被delete。如果使用一個(gè)右值來構(gòu)造對(duì)象,那么編譯器將會(huì)調(diào)用復(fù)制構(gòu)造函數(shù)。

例如,MemoryBlock自定了析構(gòu)函數(shù),賦值構(gòu)造函數(shù),賦值運(yùn)算符,于是默認(rèn)的移動(dòng)構(gòu)造和移動(dòng)運(yùn)算符就會(huì)被delete。

即便你使用了MemoryBlock m2(std::move(m1));,其仍然調(diào)用的是復(fù)制構(gòu)造函數(shù)。

#include <iostream>
#include <algorithm>

class MemoryBlock
{
public:

   // Simple constructor that initializes the resource.
   explicit MemoryBlock(size_t length)
      : _length(length)
      , _data(new int[length])
   {
      std::cout << "In MemoryBlock(size_t). length = "
                << _length << "." << std::endl;
   }

   // Destructor.
   ~MemoryBlock()
   {
      std::cout << "In ~MemoryBlock(). length = "
                << _length << ".";

      if (_data != nullptr)
      {
         std::cout << " Deleting resource.";
         // Delete the resource.
         delete[] _data;
      }

      std::cout << std::endl;
   }

   // Copy constructor.
   MemoryBlock(const MemoryBlock& other)
      : _length(other._length)
      , _data(new int[other._length])
   {
      std::cout << "In MemoryBlock(const MemoryBlock&). length = "
                << other._length << ". Copying resource." << std::endl;

      std::copy(other._data, other._data + _length, _data);
   }

   // Copy assignment operator.
   MemoryBlock& operator=(const MemoryBlock& other)
   {
      std::cout << "In operator=(const MemoryBlock&). length = "
                << other._length << ". Copying resource." << std::endl;

      if (this != &other)
      {
         // Free the existing resource.
         delete[] _data;

         _length = other._length;
         _data = new int[_length];
         std::copy(other._data, other._data + _length, _data);
      }
      return *this;
   }

   // Retrieves the length of the data resource.
   size_t Length() const
   {
      return _length;
   }

private:
   size_t _length; // The length of the resource.
   int* _data; // The resource.
};

int main()
{
    MemoryBlock m1(10);
    MemoryBlock m2(std::move(m1));
}

因此這就誕生了另一個(gè)著名定理rule of five定理。即如果你需要自定義移動(dòng)構(gòu)造函數(shù)和移動(dòng)運(yùn)算符,那么大概率你需要自定義5個(gè)特殊函數(shù)(析構(gòu)函數(shù),復(fù)制構(gòu)造函數(shù),賦值運(yùn)算符,移動(dòng)構(gòu)造函數(shù),移動(dòng)運(yùn)算符)。

這里順便再提到另一個(gè)rule of zero定理,

  • 1.類不應(yīng)定義任何特殊函數(shù)(復(fù)制/移動(dòng)構(gòu)造函數(shù)/賦值和析構(gòu)函數(shù)),除非它們是專用于資源管理的類。

此舉為了滿足設(shè)計(jì)上的單一責(zé)任原則,將數(shù)據(jù)模塊與功能模塊在代碼層面分離,降低耦合度。

class rule_of_zero
{
    std::string cppstring;
public:
    rule_of_zero(const std::string& arg) : cppstring(arg) {}
};
  • 2.基類作為管理資源的類在被繼承時(shí),析構(gòu)函數(shù)可能必須要聲明為public virtual,這樣的行為會(huì)破壞移動(dòng)復(fù)制構(gòu)造,因此,如果基類在此時(shí)的默認(rèn)函數(shù)應(yīng)設(shè)置為default。

此舉為了滿足多態(tài)類在C ++核心準(zhǔn)則中禁止復(fù)制的編碼原則。

class base_of_five_defaults
{
public:
    base_of_five_defaults(const base_of_five_defaults&) = default;
    base_of_five_defaults(base_of_five_defaults&&) = default;
    base_of_five_defaults& operator=(const base_of_five_defaults&) = default;
    base_of_five_defaults& operator=(base_of_five_defaults&&) = default;
    virtual ~base_of_five_defaults() = default;
};

關(guān)于這個(gè)點(diǎn),還是需要一個(gè)例子來加深印象:

#include <iostream>
#include <algorithm>
#include <vector>

class A{
public:
    A() {
        std::cout << "A()" << std::endl;
    };
    ~A() = default;
    A(const A& other){
        std::cout << "A(const A& other)" << std::endl;
    }
    A& operator=(const A& other){
        std::cout << "operator=(const A& other)" << std::endl;
        return *this;
    }
    A(A&& other){
        std::cout << "A(A&& other)" << std::endl;
    }
    A& operator=(A&& other){
        std::cout << "operator=(A&& other)" << std::endl;
        return *this;
    }
};

class DataMgr {
public:
    DataMgr(){
        val_.reserve(10);
    }
    virtual ~DataMgr() = default;
    // DataMgr(const DataMgr& other) = default;
    // DataMgr& operator=(const DataMgr& other) = default;
    // DataMgr(DataMgr&& other) = default;
    // DataMgr& operator=(DataMgr&& other) = default;

public:
    void push(A& a){
        val_.emplace_back(a);
    }
private:
    std::vector<A> val_;              //同之前一樣
};

int main()
{
    A a1, a2;
    DataMgr s1;
    s1.push(a1);
    s1.push(a2);
    std::cout << "========" << std::endl;
    DataMgr s2 ;
    s2 = std::move(s1);
}

這里的運(yùn)行結(jié)果如下所示:

A()
A()
A(const A& other)
A(const A& other)
========
A(const A& other)
A(const A& other)

盡管使用了s2 = std::move(s1)這里使用了移動(dòng)語義,然而由于定義了析構(gòu)函數(shù),移動(dòng)操作被delete,導(dǎo)致了調(diào)用了復(fù)制構(gòu)造。試想如果這里的val_的數(shù)據(jù)量很大,那么程序的運(yùn)行效率將會(huì)相差很大。文章來源地址http://www.zghlxwxcb.cn/news/detail-679614.html

總結(jié)

  • 特殊成員函數(shù)是編譯器可能自動(dòng)生成的函數(shù),它包括下面六種默認(rèn)構(gòu)造函數(shù),析構(gòu)函數(shù),復(fù)制構(gòu)造函數(shù),賦值運(yùn)算符,移動(dòng)構(gòu)造函數(shù),移動(dòng)運(yùn)算符。
  • 對(duì)于構(gòu)造函數(shù)而言,如果需要自定義初始化成員的方式,則不能使用默認(rèn)的構(gòu)造函數(shù),需要編寫自定義構(gòu)造函數(shù)。
  • 對(duì)于析構(gòu)函數(shù)而言,如果其內(nèi)部管理了資源(原始指針,文件描述符,線程等等),則通常需要編寫自定義的析構(gòu)函數(shù)。如果只是借用資源,通常使用默認(rèn)析構(gòu)函數(shù)就可以。
  • 根據(jù)rule of three,析構(gòu)函數(shù)進(jìn)行了自定義,大概率你也需要自定義復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符。
  • 默認(rèn)移動(dòng)操作僅當(dāng)類沒有顯式聲明移動(dòng)操作,復(fù)制操作,析構(gòu)函數(shù)時(shí)才自動(dòng)生成。如果你定義了析構(gòu)函數(shù)或者復(fù)制操作,此時(shí)的移動(dòng)操作會(huì)調(diào)用復(fù)制構(gòu)造函數(shù)。
  • 如果一個(gè)類沒有顯示定義復(fù)制構(gòu)造卻顯示定義了移動(dòng)構(gòu)造,則復(fù)制構(gòu)造函數(shù)被delete。同理如果一個(gè)類沒有顯示定義賦值運(yùn)算符卻顯示定義了移動(dòng)運(yùn)算符,則賦值運(yùn)算符數(shù)被delete。
  • 日常開發(fā)中,盡量顯示指明是否使用default的特殊函數(shù)以避免某些成員函數(shù)被delete。如果某些方法不需要生成,則應(yīng)該delete掉。

到了這里,關(guān)于深入理解c++特殊成員函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【C++系列P5】‘類與對(duì)象‘-三部曲——[對(duì)象&特殊成員](3/3)

    【C++系列P5】‘類與對(duì)象‘-三部曲——[對(duì)象&特殊成員](3/3)

    ?前言 大家好吖,歡迎來到 YY 滴 C++系列 ,熱烈歡迎! 【 \\\'類與對(duì)象\\\'-三部曲】的大綱主要內(nèi)容如下 : 如標(biāo)題所示,本章是【 \\\'類與對(duì)象\\\'-三部曲】三章中的第三章節(jié)——對(duì)象成員章節(jié),主要內(nèi)容如下: 目錄 一.const成員/成員函數(shù) 一.用const修飾this指針的好處——含權(quán)限知識(shí)點(diǎn)

    2024年02月06日
    瀏覽(22)
  • 【C++】:類和對(duì)象(下):explicit || 再談構(gòu)造函數(shù) || static成員 || 友元 || 內(nèi)部類 || 匿名對(duì)象 || 拷貝對(duì)象時(shí)的編譯器優(yōu)化問題 || 再次理解類和對(duì)象

    【C++】:類和對(duì)象(下):explicit || 再談構(gòu)造函數(shù) || static成員 || 友元 || 內(nèi)部類 || 匿名對(duì)象 || 拷貝對(duì)象時(shí)的編譯器優(yōu)化問題 || 再次理解類和對(duì)象

    ??類和對(duì)象(下篇) ??【本節(jié)目標(biāo)】 ??1. 再談構(gòu)造函數(shù) ??2. Static成員 ??3. 友元 ??4. 內(nèi)部類 ??5.匿名對(duì)象 ??6.拷貝對(duì)象時(shí)的一些編譯器優(yōu)化 ??7. 再次理解類和對(duì)象 ??1.1 構(gòu)造函數(shù)體賦值 在創(chuàng)建對(duì)象時(shí),編譯器通過調(diào)用構(gòu)造函數(shù),給對(duì)象中各個(gè)成員變量一個(gè)合適的初始值

    2024年01月21日
    瀏覽(26)
  • 深入理解和應(yīng)用C++ std::shared_ptr別名構(gòu)造函數(shù)

    在現(xiàn)代C++中,智能指針是一個(gè)極為重要的工具,尤其std::shared_ptr以其自動(dòng)內(nèi)存管理、引用計(jì)數(shù)和多線程安全性等特性深受開發(fā)者喜愛。其中一個(gè)不太常用但功能強(qiáng)大的構(gòu)造方式是 別名構(gòu)造函數(shù) ,它允許我們創(chuàng)建一個(gè)共享相同底層對(duì)象但是指向其內(nèi)部不同數(shù)據(jù)成員或子對(duì)象的

    2024年01月16日
    瀏覽(31)
  • 【C++】靜態(tài)成員函數(shù) ( 靜態(tài)成員函數(shù)概念 | 靜態(tài)成員函數(shù)聲明 | 靜態(tài)成員函數(shù)訪問 | 靜態(tài)成員函數(shù)只能訪問靜態(tài)成員 )

    【C++】靜態(tài)成員函數(shù) ( 靜態(tài)成員函數(shù)概念 | 靜態(tài)成員函數(shù)聲明 | 靜態(tài)成員函數(shù)訪問 | 靜態(tài)成員函數(shù)只能訪問靜態(tài)成員 )

    靜態(tài)成員函數(shù)歸屬 : 在 C++ 類中 , 靜態(tài)成員函數(shù) 是一種 特殊的函數(shù) , 該函數(shù)屬于類 , 而不是屬于 類實(shí)例對(duì)象 ; 靜態(tài)成員函數(shù)調(diào)用不依賴于對(duì)象 : 即使 沒有創(chuàng)建 類 的 實(shí)例對(duì)象 , 也可以 通過 類名:: 調(diào)用 類中定義的 靜態(tài)成員函數(shù) ; 靜態(tài)成員函數(shù)作用 : 靜態(tài)成員函數(shù) 通常用于

    2024年01月21日
    瀏覽(33)
  • Java開發(fā) - 深入理解Redis哨兵機(jī)制原理

    Java開發(fā) - 深入理解Redis哨兵機(jī)制原理

    Redis的主從、哨兵模式、集群模式,在前文中都已經(jīng)有了詳細(xì)的搭建流程,可謂是手把手教程,也得到了很多朋友的喜歡。由于前文偏向于應(yīng)用方面,就導(dǎo)致了理論知識(shí)的匱乏,我們可能會(huì)用了,但卻不明所以,所以今天,博主就通過接下里的幾篇博客給大家分別講解Redis哨兵

    2024年02月17日
    瀏覽(29)
  • Java開發(fā) - 深入理解Redis Cluster的工作原理

    Java開發(fā) - 深入理解Redis Cluster的工作原理

    前面我們講過Redis Cluster的搭建方式,也是本著應(yīng)用優(yōu)先的原則,所以對(duì)其基礎(chǔ)概念和原理幾乎沒有涉及,但當(dāng)學(xué)會(huì)了Redis集群的搭建方式之后,對(duì)于其原來我們還是要知道一些的,所以這篇博客,我們將一起來學(xué)習(xí)Redis Cluster的一些相關(guān)知識(shí)。 在開始Redis Cluster的講解之前,還

    2024年02月15日
    瀏覽(27)
  • 【C++】類的默認(rèn)成員函數(shù)----const成員函數(shù)(超詳細(xì)解析)

    【C++】類的默認(rèn)成員函數(shù)----const成員函數(shù)(超詳細(xì)解析)

    目錄 一、前言 二、const成員函數(shù)? ??const修飾類的成員函數(shù)? ??問題1? ??問題2 ??針對(duì)const成員函數(shù)的??济嬖囶}(重點(diǎn)?。。???取地址及const取地址操作符重載 三、共勉 ?? 在我們前面學(xué)習(xí)的 類 中,我們會(huì)定義 成員變量 和 成員函數(shù) ,這些我們自己定義的函數(shù)都是普

    2024年04月14日
    瀏覽(19)
  • C++:常成員變量、常成員函數(shù)、常對(duì)象

    C++:常成員變量、常成員函數(shù)、常對(duì)象

    常成員變量: 1.用const修飾,可位于類型前后,若是成員變量類型為指針則只可位于類型后。 即:int? *const? p; 2.只能通過構(gòu)造函數(shù)的初始化表對(duì)常成員變量進(jìn)行初始化。 3.常成員所在類中的所有構(gòu)造函數(shù)都必須對(duì)常成員變量初始化(通過初始化表)。 4.常成員變量可以被訪

    2024年02月11日
    瀏覽(34)
  • C++ 學(xué)習(xí) ::【基礎(chǔ)篇:13】:C++ 類的基本成員函數(shù):類類型成員的初始化與構(gòu)造函數(shù)問題

    C++ 學(xué)習(xí) ::【基礎(chǔ)篇:13】:C++ 類的基本成員函數(shù):類類型成員的初始化與構(gòu)造函數(shù)問題

    本系列 C++ 相關(guān)文章 僅為筆者學(xué)習(xí)筆記記錄,用自己的理解記錄學(xué)習(xí)!C++ 學(xué)習(xí)系列將分為三個(gè)階段: 基礎(chǔ)篇、STL 篇、高階數(shù)據(jù)結(jié)構(gòu)與算法篇 ,相關(guān)重點(diǎn)內(nèi)容如下: 基礎(chǔ)篇 : 類與對(duì)象 (涉及C++的三大特性等); STL 篇 : 學(xué)習(xí)使用 C++ 提供的 STL 相關(guān)庫(kù) ; 高階數(shù)據(jù)結(jié)構(gòu)與算

    2024年02月08日
    瀏覽(23)
  • 再探C++——默認(rèn)成員函數(shù)

    再探C++——默認(rèn)成員函數(shù)

    目錄 一、構(gòu)造函數(shù) 二、析構(gòu)函數(shù) 三、賦值運(yùn)算符 四、拷貝構(gòu)造 如果一個(gè)類中沒有成員,我們稱為空類。空類,也存在6個(gè)默認(rèn)的類成員函數(shù)。 默認(rèn)成員函數(shù):用戶不顯示地寫,編譯器會(huì) 默認(rèn)生成 的函數(shù)叫做默認(rèn)成員函數(shù)。 6個(gè)默認(rèn)成員函數(shù): 構(gòu)造函數(shù):完成對(duì)象初始化?

    2024年02月14日
    瀏覽(19)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包