C++ オブジェクト呼び出しの最適化

C++ オブジェクト呼び出しの最適化

一時オブジェクトは新しいオブジェクトを構築するためにコピーされますが、一時オブジェクトは生成されません

共通オブジェクト呼び出し処理

C++ コンパイラはオブジェクトの構築を最適化します。一時オブジェクトを使用して新しいオブジェクトをコピーする場合、一時オブジェクトは生成されず、新しいオブジェクトを直接構築できます

#include<iostream>
using namespace std;

class Test
{
    
    
public:
    Test(int data = 10) : ma(data), mb(data) {
    
     cout << "Test(int)" << endl; }
    ~Test() {
    
     cout << "~Test()" << endl; }
    Test(const Test &t) {
    
     cout << "Test(&)" << endl; }
    Test &operator=(const Test &t) {
    
     cout << "operator=" << endl; }

private:
    int ma;
    int mb;
};
int main()
{
    
    
    Test t1;//普通构造函数
    Test t2(t1);//拷贝构造函数
    Test t3 = t1; // 拷贝构造函数

    t2 = t3;//拷贝构造函数

    // 这里和Test t4(20)没有区别,并不会产生Test(20)临时对象
    Test t4 = Test(20);//普通构造函数
    cout << "-------------------" << endl;

    //显示调用构造函数生成临时对象
    t4 = Test(30); // 普通构造函数,结束这条语句就会调用析构函数
    t4 = (Test)30; // 普通构造函数,结束这条语句就会调用析构函数

    //隐式调用构造函数生成临时对象
    t4 = 30; // 普通构造函数,结束这条语句就会调用析构函数
    cout << "-----------------" << endl;

    return 0;
}


const Test &t1 = Test(20, 20);//通常のコンストラクターを呼び出すだけで、一時オブジェクトは生成されません (したがって、破壊はありません)。

オブジェクトの作成順序

static Test t4 = Test(30, 30);ここでコンパイラが最適化を行い、一時オブジェクトは新しいオブジェクトを生成し、一時オブジェクトは生成されないことに注意してください。これは静的 Test t4(30,30) と同等ですTest *p3 = &Test(80, 80);ここで、ステートメントは終了後に破棄され、p3 は null ポインターです。const Test &p4 = Test(90, 90);の場合、参照変数は一時オブジェクトを保持するため、破棄されません。静的オブジェクトはスコープの終了後に破棄されます。

#include<iostream>
using namespace std;

class Test
{
    
    
public:
    Test(int x = 10, int y = 10) : ma(x), mb(y) {
    
     cout << "Test(int,int)" << endl; }
    ~Test() {
    
     cout << "~Test()" << endl; }
    Test(const Test &t) {
    
     cout << "Test(&)" << endl; }
    Test &operator=(const Test &t) {
    
     cout << "operator=" << endl; }

private:
    int ma;
    int mb;
};
Test t1(10, 10);//1.Test(int,int)
int main()
{
    
    

    Test t2(20, 20);//3.Test(int,int)
    Test t3 = t2;   // 4.Test(&)
    // 这里编译器会优化,临时对象生成新对象,不会产生临时对象,等价于static Test t4(30,30)
    static Test t4 = Test(30, 30); // 5.Test(int,int),
    t2 = Test(40, 40);             // 6.Test(int,int),operator=,~Test()
    t2 = (Test)(50, 50);           // 7.Test(int,int),operator=,~Test()
    t2 = 60;                       // 8.Test(int,int),operator=,~Test()
    Test *p1 = new Test(70, 70);   // 9.Test(int,int)
    Test *p2 = new Test[2];        // 10.Test(int,int),Test(int,int)
    // Test *p3 = &Test(80, 80);      //可能会报错// 11.Test(int,int),~Test(),这里语句结束后就被析构,p3就是空指针
    const Test &p4 = Test(90, 90); // 12.Test(int,int),引用变量就会保存临时对象,所以不会被析构
    delete p1;                     // 13.~Test()
    delete[] p2;                   // 14.~Test()
    return 0;
}
Test t5(100, 100);//2.Test(int,int)
/*
剩下的析构顺序,p4->t3->t2->t4->t5->t1
t4在t3和t2后面析构的原因是它是静态变量,存储在数据段中,所以在后面析构
*/

関数呼び出しの問題

関数呼び出しの過程にはオブジェクトの最適化問題が数多く存在しますが、その最適化方法については後ほど詳しく紹介します。

#include<iostream>
using namespace std;

class Test
{
    
    
public:
    Test(int data = 10) : ma(data) {
    
     cout << "Test(int)" << endl; }
    Test(const Test& t) {
    
     cout << "Test(&)" << endl; }
    Test& operator=(const Test& t) {
    
     cout << "operator=" << endl; return *this; }
    ~Test() {
    
     cout << "~Test()" << endl; }
    int getData() {
    
     return ma; }

private:
    int ma;
};
Test getObj(Test t)
{
    
    
    int val = t.getData();
    Test temp(val);
    return temp;
}
int main()
{
    
    
    Test t1;
    Test t2;

    cout << "------------------" << endl;
    t2 = getObj(t1);
    cout << "------------------" << endl;
    Test t3 = getObj(t2);
    cout << "------------------" << endl;

    return 0;
}


関数オブジェクトの呼び出しプロセスのグラフ。

関数の最適化: 値を渡す代わりに参照を渡すようにし、関数内でオブジェクトを構築する代わりに一時オブジェクトを返すようにします。最適化されたコードは次のようになります。

Test getObj(Test& t)
{
    
    
    int val = t.getData();
    //Test temp(val);
    return Test(val);
}

これにより、実パラメータから仮パラメータへのコピー構築、および関数内のオブジェクトの構築と破棄を削減できます。

関数呼び出しの最適化まとめ!

関数の受け渡し中、オブジェクトは値ではなく参照によって最初に渡されます

関数がオブジェクトを返すときは、定義されたオブジェクトを返すのではなく、一時オブジェクトを返すようにしてください

受信の戻り値がオブジェクトの場合は、代入の形式ではなく初期化の形式で受信するようにしてください

具体的には、関数呼び出しの既存の問題を説明および分析できます。

文字列右辺値参照のコピー構築と演算子のオーバーロード

#include<iostream>
#include<string.h>

using namespace std;

class String
{
    
    
public:
    String(const char* p = nullptr)
    {
    
    
        if (p != nullptr)
        {
    
    
            _pstr = new char[strlen(p) + 1];
            strcpy(_pstr, p);
        }
        else
        {
    
    
            _pstr = new char[1];
            *_pstr = '\0';
        }
        cout << "String(const char*)" << endl;
    }
    ~String()
    {
    
    
        cout << "~String()" << endl;
        delete[] _pstr;
        _pstr = nullptr;
    }
    String(const String& src)
    {
    
    
        cout << "String(const String&)" << endl;
        _pstr = new char[strlen(src._pstr) + 1];
        strcpy(_pstr, src._pstr);
    }
    String(String&& src)
    {
    
    
        //因为是临时对象,所以就没有用const修饰
        cout << "String(const String&&)" << endl;
        _pstr = src._pstr;
        src._pstr = nullptr;
    }
    String& operator=(const String& src)
    {
    
    
        cout << "String& operator=(const String&)" << endl;
        if (this == &src)
            return *this;
        delete[] _pstr;
        _pstr = new char[strlen(src._pstr) + 1];
        strcpy(_pstr, src._pstr);
        return *this;
    }
    String& operator=(String&& src)
    {
    
    
        //右值引用的操作符重载函数
        cout << "String& operator=(String&&)" << endl;
        if (this == &src)
            return *this;
        delete[] _pstr;
        _pstr = src._pstr;
        src._pstr = nullptr;
        return *this;
    }
    bool operator>(const String& src) const
    {
    
    
        return strcmp(_pstr, src._pstr) > 0;
    }
    bool operator==(const String& src) const
    {
    
    
        return strcmp(_pstr, src._pstr) == 0;
    }
    bool operator<(const String& src) const
    {
    
    
        return strcmp(_pstr, src._pstr) < 0;
    }
    int length()const
    {
    
    
        return strlen(_pstr);
    }
    char& operator[](int index) {
    
     return _pstr[index]; }
    const char& operator[](int index) const {
    
     return _pstr[index]; }
    const char* c_str() const {
    
     return _pstr; }

private:
    char* _pstr;
    friend String operator+(const String& lsrc, const String& rsrc);
    friend ostream& operator<<(ostream& out, const String& src);
    friend istream& operator>>(istream& in, String& src);
};

String operator+(const String& lsrc, const String& rsrc)
{
    
    
    char* temp = new char[strlen(lsrc._pstr) + strlen(rsrc._pstr) + 1];
    strcpy(temp, lsrc._pstr);
    strcat(temp, rsrc._pstr);
    String s(temp);
    delete[] temp;
    return s;
}
ostream& operator<<(ostream& out, const String& src)
{
    
    
    out << src._pstr;
    return out;
}
istream& operator>>(istream& in, String& src)
{
    
    
    in >> src._pstr;
    return in;
}

int main()
{
    
    
    String str1;
    String str2 = "aaa";
    String str3 = "bbb";
    String str4 = str2 + str3;
    String str5 = str2 + "ccc";
    String str6 = "ddd" + str2;
    
    cout << "----------------------------------" << endl;
    String str7 = String("hello,world");//C++会优化,直接调用构造函数,不会产生临时对象
    str6 = String("hello") + String(",world");//先调用两个构造函数,在调用operator+重载,
    cout << "----------------------------------" << endl;

    return 0;
}

右辺値演算子とコピー コンストラクターがない場合:


右辺値参照を使用したコピー構築および演算子オーバーロード関数が設定されます。


一時オブジェクトを使用した元のオブジェクト生成のすべての関数が、右辺値参照を持つコピー コンストラクターによって置き換えられ、代入演算子のオーバーロードも右辺値参照を持つコピー コンストラクターによって置き換えられることがわかります。右辺値参照を使用する利点は、主に、コピー構築時および演算子のオーバーロード時のリソースの作成と解放を節約できることです。

ベクターでの文字列の使用

&& 前に移動

右辺値参照変数自体は lvalue であるため、関数に右辺値参照変数が渡されると、次のように問題が発生する可能性があります。

void construct(T* p, const T& val)//对象创建
{
    
    
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    new (p) T(val);//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void construct(T* p, T&& val)//对象创建
{
    
    
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    //这里虽然val是一个右值引用变量,但是还是会调用左值的拷贝构造函数
    new (p) T(val);//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void push_back(T&& val)
{
    
    
    if (full())
        expand();
    //虽然传入的val是一个右值引用变量,但是它本身是一个左值,
    //所以还是会调用void construct(T* p, const T& val)而不是带右值引用的函数
    _allocator.construct(_last, val);
    _last++;
}

move: 移動セマンティクス、右辺値型を取得するための強制。左辺値を右辺値に変換することが実現できます (この値は実際には右辺値参照変数である可能性があります)。
forward: 型の完全な変換。左辺値型と右辺値型を認識できます。左辺値であることが判明した場合は左辺値のままであり、右辺値であることが判明した場合は右辺値として渡されます。

String& &&val->String&1 つの引用と 2 つの引用は、依然として 1 つの引用です。
String&& &&val->String&2 つの引用と 2 つの引用、または 2 つの引用。
String&& &val->String&右辺値参照自体が左辺値であるため、2 つの参照と 1 つの参照は依然として参照です。

次の val は、実際には関数の受け渡しプロセスにおける仮パラメータの型です。

したがって、上記のコードは次の 2 つのタイプに変更されると考えることができます。

方法 1: 強制変換を移動する

void construct(T* p, const T& val)//对象创建
{
    
    
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    new (p) T(val);//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void construct(T* p, T&& val)//对象创建
{
    
    
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    new (p) T(std::move(val));//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void push_back(const T& val)
{
    
    
    if (full())
        expand();
    //虽然传入的val是一个右值引用变量,但是它本身是一个左值,
    //所以还是会调用void construct(T* p, const T& val)而不是带右值引用的函数
    _allocator.construct(_last, val);
    _last++;
}
void push_back(T&& val)
{
    
    
    if (full())
        expand();
    _allocator.construct(_last, std::move(val));
    _last++;
}

方法 2: 前方完全変換

//不管val传递的是左值还是右值,T&& val都能接收
//如果是左值,则接收后还是左值;反之右值,则还是右值
void construct(T* p, T&& val)//对象创建
{
    
    
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    new (p) T(std::forward(val));//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void push_back(T&& val)
{
    
    
    if (full())
        expand();
    //虽然传入的val是一个右值引用变量,但是它本身是一个左值,
    //所以还是会调用void construct(T* p, const T& val)而不是带右值引用的函数
    _allocator.construct(_last, std::forward(val));
    _last++;
}

おすすめ

転載: blog.csdn.net/qq_45041871/article/details/132499166