如何初始化一个共享指针成员?

huangapple go评论74阅读模式
英文:

How should I initialise a shared-pointer member?

问题

  1. 传递引用:

    class MyClass {
        std::shared_ptr<std::string> s1;
    public:
        MyClass(std::shared_ptr<std::string>& s2): s1{s2} {}
    };
    
  2. 传递值

    class MyClass2 {
        std::shared_ptr<std::string> s1;
    public:
        MyClass2(std::shared_ptr<std::string> s2): s1{std::move(s2)} {}
    };
    

测试程序:

int main() {
    std::shared_ptr<std::string> s2 = std::make_shared<std::string>("Hello World");
    MyClass myClass {s2};
    MyClass2 myClass2 {s2};
    
    std::cout << "Use count:" << s2.use_count() << std::endl;
        
    return 0;
}

结果:

Use count:3

我认为第二种方式没有比第一种方式更有优势。
我认为第二种方式不应该使用,因为多了一个移动操作。

在性能方面,你认为哪种方式更好来初始化std::shared_ptr

更新

我可以根据下面的代码更新MyClass,以便可以像这样调用它:

MyClass myClass {std::make_shared<std::string>("Hello World")};

性能在太多循环中可能会有所不同。

基准测试

#include <iostream>
#include <memory>
#include <chrono>

using namespace std;

class MyClass {
    std::shared_ptr<std::string> s1;
public:
    MyClass(const std::shared_ptr<std::string>& s2): s1{s2} {}
};

class MyClass2 {
    std::shared_ptr<std::string> s1;
public:
    MyClass2(std::shared_ptr<std::string> s2): s1{std::move(s2)} {}
};


int main() {
    std::shared_ptr<std::string> s2 = std::make_shared<std::string>("Hello World");
    
    auto start = std::chrono::high_resolution_clock::now();
    
    for (int i = 0; i < 10000000; i++) {
        MyClass myClass {s2};
    }    
    auto end = std::chrono::high_resolution_clock::now();
    
    std::chrono::duration<float> duration = end - start;
    std::cout << duration.count() << std::endl;
    
    
    start = std::chrono::high_resolution_clock::now();
    
    for (int i = 0; i < 10000000; i++) {
        MyClass2 myClass2 {s2};
    }    
    end = std::chrono::high_resolution_clock::now();
    
    duration = end - start;
    std::cout << duration.count() << std::endl;    
        
    return 0;
}

在线C++编译器和C++17:

0.358915
0.623789
英文:

I can initialize a std::shared_ptr member in 2 different ways in my constructor (which both increment its use_count):

  1. Pass by reference:

    class MyClass {
        std::shared_ptr&lt;std::string&gt; s1;
    public:
        MyClass(std::shared_ptr&lt;std::string&gt;&amp; s2): s1{s2} {}
    };
    
  2. Pass by value

    class MyClass2 {
        std::shared_ptr&lt;std::string&gt; s1;
    public:
        MyClass2(std::shared_ptr&lt;std::string&gt; s2): s1{std::move(s2)} {}
    };
    

Test program:

int main() {
    std::shared_ptr&lt;std::string&gt; s2 = std::make_shared&lt;std::string&gt;(&quot;Hello World&quot;);
    MyClass myClass {s2};
    MyClass2 myClass2 {s2};
    
    std::cout &lt;&lt; &quot;Use count:&quot; &lt;&lt; s2.use_count() &lt;&lt; std::endl;
        
    return 0;
}

Result:

Use count:3

I don't see any advantage of the 2nd way over the 1st way.
I think the 2nd way should never be used because there is an extra move operation.

Which way do you think is better to initialise a std::shared_ptr, in terms of performance?

Updated

I can update MyClass according to below so it can be called like:
MyClass myClass {std::make_shared&lt;std::string&gt;(&quot;Hello World&quot;)};

class MyClass {
    std::shared_ptr&lt;std::string&gt; s1;
public:
    MyClass(const std::shared_ptr&lt;std::string&gt;&amp; s2): s1{s2} {}
};

Performance I guess makes a difference in too many loops.

Benchmark

#include &lt;iostream&gt;
#include &lt;memory&gt;
#include &lt;chrono&gt;

using namespace std;

class MyClass {
    std::shared_ptr&lt;std::string&gt; s1;
public:
    MyClass(const std::shared_ptr&lt;std::string&gt;&amp; s2): s1{s2} {}
};

class MyClass2 {
    std::shared_ptr&lt;std::string&gt; s1;
public:
    MyClass2(std::shared_ptr&lt;std::string&gt; s2): s1{std::move(s2)} {}
};


int main() {
    std::shared_ptr&lt;std::string&gt; s2 = std::make_shared&lt;std::string&gt;(&quot;Hello Wolrd&quot;);
    
    auto start = std::chrono::high_resolution_clock::now();
    
    for (int i = 0; i &lt; 10000000; i++) {
        MyClass myClass {s2};
    }    
    auto end = std::chrono::high_resolution_clock::now();
    
    std::chrono::duration&lt;float&gt; duration = end - start;
    std::cout &lt;&lt; duration.count() &lt;&lt; std::endl;
    
    
    
    start = std::chrono::high_resolution_clock::now();
    
    for (int i = 0; i &lt; 10000000; i++) {
        MyClass2 myClass2 {s2};
    }    
    end = std::chrono::high_resolution_clock::now();
    
    duration = end - start;
    std::cout &lt;&lt; duration.count() &lt;&lt; std::endl;    
        
    return 0;
}

Online c++ compiler and c++17

0.358915
0.623789

答案1

得分: 9

第一种方式需要至少一个shared_ptr的副本(从s2s1),无论调用函数做什么,都需要获取锁、增加和减少引用计数以及其他开销。此外,由于第一种版本需要一个shared_ptr&amp;,所以如果调用函数有一个临时的shared_ptr&amp;&amp;,那么它们被迫将其存储在变量中,然后构造该类:

std::shared_ptr<std::string> s2 = std::make_shared<std::string>("Hello World");
MyClass myClass {s2};

第二种方式允许调用者将一个shared_ptr值移入构造函数,然后直接移动到s1。移动操作更加便宜:它们不需要锁定,并且不会增加或减少引用计数。它们非常快速。此外,由于第二种方式只接受一个值,因此可以以任何方式构造,因此可以直接从临时值构造:

MyClass myClass {std::make_shared<std::string>("Hello World")};

因此,第二种方式在各个方面都更优越。

在我的测试中,与您的测试相似,move版本快约10%。
https://quick-bench.com/q/P3pX-bC8kRXSEmqJw2_KSz-kc0s

英文:

The first way requires at least one copy of the shared_ptr (from s2 to s1), regardless of what the calling function does, which requires grabbing locks, incrementing and decrementing the reference count, and other overhead.
Also, since the first version requires a shared_ptr&amp;, then if the calling function has a temporary shared_ptr&amp;&amp;, then they're forced to store it in a variable before constructing the class:

    std::shared_ptr&lt;std::string&gt; s2 = std::make_shared&lt;std::string&gt;(&quot;Hello World&quot;);
    MyClass myClass {s2};

The second way allows the caller to move a shared_ptr value into the constructor, which is then moved directly into s1. Move operations are cheaper: they do not require locks, and they do not increment or decrement the reference count. They're insanely fast. Also, since the second one just takes a value, it can be constructed in any way, and therefore can be constructed directly from temporaries:

    MyClass myClass {std::make_shared&lt;std::string&gt;(&quot;Hello World&quot;)};

So the second way is superior in every way.

In my tests that mirror your tests, the move version is ~10% faster.
https://quick-bench.com/q/P3pX-bC8kRXSEmqJw2_KSz-kc0s

huangapple
  • 本文由 发表于 2023年7月27日 22:30:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76780761.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定