

C++11には`weak`, `shared`, `unique`のスマートポインタがありますが、これらが使える環境であえてnew,
deleteだけを使ったインスタンスの生成を使う意味はありますか?

C++11ではnew, deleteだけを使った方法は、使うべきではない方法という位置付けなのでしょうか?

後方互換性は考えないものとします


CC BY-SA 3.0
2017-03-31T06:38:26.527
33665
2017-07-25T17:13:54.193
5246
question
c++ c++11
C++11のスマートポインタが使える場合に、new, deleteは必要なのか


newを使う理由の一つとして速度があるのではないかと思い、検証を行いました。以下、その検証結果です。

[code]

    #include <chrono>
    #include <cstdlib>
    #include <cstring>
    #include <functional>
    #include <iomanip>
    #include <iostream>
    #include <memory>
    
    struct Test {
        int v;
        Test(int v) : v(v){};
        int f(int i)
        {
            v += i;
            return v;
        }
    };
    
    auto benchmark(const std::function<void(int)> &func, const int count = 1)
    {
        auto start = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < count; ++i) func(i);
        auto end = std::chrono::high_resolution_clock::now();
        return std::chrono::duration_cast<std::chrono::nanoseconds>(end - start)
            .count();
    }
    
    int main()
    {
        const int count = 100000;
        int n;
        // stack
        n = 0;
        auto stack_t = benchmark(
            [&n](int i) {
                Test t(i);
                n += t.f(i);
            },
            count);
        std::cout << " stack: " << std::setw(12) << stack_t << "ns, " << n
                  << std::endl;
        // malloc
        n = 0;
        auto malloc_t = benchmark(
            [&n](int i) {
                auto t = static_cast<Test *>(std::malloc(sizeof(Test)));
                t->v = i;
                n += t->f(i);
                std::free(t);
            },
            count);
        std::cout << "malloc: " << std::setw(12) << malloc_t << "ns, " << n
                  << std::endl;
        // new
        n = 0;
        auto new_t = benchmark(
            [&n](int i) {
                auto t = new Test(i);
                n += t->f(i);
                delete t;
            },
            count);
        std::cout << "   new: " << std::setw(12) << new_t << "ns, " << n
                  << std::endl;
        // unique
        n = 0;
        auto unique_t = benchmark(
            [&n](int i) {
                auto t = std::make_unique<Test>(i);
                n += t->f(i);
            },
            count);
        std::cout << "unique: " << std::setw(12) << unique_t << "ns, " << n
                  << std::endl;
        // shared
        n = 0;
        auto shared_t = benchmark(
            [&n](int i) {
                auto t = std::make_shared<Test>(i);
                n += t->f(i);
            },
            count);
        std::cout << "shared: " << std::setw(12) << shared_t << "ns, " << n
                  << std::endl;
        return 0;
    }
    
[/code]

※ このコードはC++14向けです

上のコードをmacOSのApple LLVM version 8.1.0 (clang-802.0.38)で`clang++ -O0
-std=c++14`(最適化無し)でコンパイルして実行すると下記の結果になりました。

[code]

     stack:      2346911ns, 1409965408
    malloc:      7772341ns, 1409965408
       new:      8951401ns, 1409965408
    unique:     10864862ns, 1409965408
    shared:     22686880ns, 1409965408
    
[/code]

unique_ptrやshared_ptrはラップされている分newよりも遅くなっていると思われます。さらにshared_ptrが遅いのは参照カウンタの処理分と思われます。しかし、`clang++
-O2 -std=c++14`(最適化有り)でコンパイルすると下記の結果になりました。

[code]

     stack:       270138ns, 1409965408
    malloc:       306524ns, 1409965408
       new:       263298ns, 1409965408
    unique:       287971ns, 1409965408
    shared:      8967664ns, 1409965408
    
[/code]

shared_ptr以外はスタックに詰む場合とほとんど変わらなくなります。最適化により、newやunique_ptrはスタックとほぼ同レベルまで速くなるようです。ただ、最適化がどれほど有効になるかはコンパイラとコードに依るため、一概に最適化によって同レベルの速度になると結論づけることはできないと考えています。
**unique_ptrが最適化によってnewと同等レベルの速度に必ずなる、または、ならない場合もあるという証拠をお持ちの方は情報をお願いします。**

shared_ptrについては、参照カウンタの仕組みがあるためか、その分が遅くなっていると思われます。newと同等速度にまで最適化できる場合があるかはわかりませんでした。

なお、Homebrew GCC 6.3.0_1でコンパイルすると

[code]

    # g++-6 -O0 -std=c++14 の場合
     stack:      2711000ns, 1409965408
    malloc:      9153000ns, 1409965408
       new:     13656000ns, 1409965408
    unique:     16941000ns, 1409965408
    shared:     33897000ns, 1409965408
    # g++-6 -O2 -std=c++14 の場合
     stack:       229000ns, 1409965408
    malloc:       251000ns, 1409965408
       new:      7885000ns, 1409965408
    unique:      7101000ns, 1409965408
    shared:      8947000ns, 1409965408
    
[/code]

となり、newとunique_ptrは最適化でほぼ同等の結果になります。

※ 最新のVC++は今手元にないため、検証できる方が編集で追記いただけると助かります。

検証からの結論としては、
**最適化されなければ、newよりnuique_ptrとshared_ptrは遅くなるが、最適化によってunique_ptrはほぼ度速度になり、shared_ptrは遅いままになる場合がある**
と言えるかと思います。しかし、最適化の影響がどこまで有効かはコンパイラやコード内容によるため、安易に結論づけることはは難しいです。

* * *

**初版の検証コードは問題があったため、参考にしないでください。詳細は[sayuriさんの回答](https://ja.stackoverflow.com/a/33723/7347)を参照してください。**


CC BY-SA 3.0
2017-04-01T05:55:26.540
33689
2017-04-04T12:45:24.637
2017-04-13T12:52:38.920
-1
7347
33665
answer


らっしーさんの回答は更新されて`new`と`unique`とで大差ない値が出てしまっているので、[更新前](https://ja.stackoverflow.com/revisions/33689/1)、何が起こっていたのかを説明しておきます。

当たり前と感じるかもしれませんが、

[code]

    int i0;
    
[/code]

と記述した場合、C言語互換で未初期化の変数`i0`が用意されます。これを

[code]

    int i1 = int();
    int i2{};
    
[/code]

などの記述をするとデフォルトコンストラクターが実行され`0`で初期化されます。同様のことが`new`でも存在し

[code]

    int* j0 = new int;
    int* j1 = new int();
    int* j2 = new int{};
    
[/code]

`j0`はメモリ確保は行われますが未初期化のままであり、`j1`および`j2`はメモリ確保の後にデフォルトコンストラクターによって`0`で初期化されます。  
そして本題、

[code]

    std::unique_ptr<int> k = std::make_unique<int>();
    
[/code]

ですとどうしてもデフォルトコンストラクターを呼び出してしまうため強制的に初期化のコストがかかります。

また`unique_ptr`は動的に`release()`可能です。そのため、`unique_ptr`デストラクターでは保持しているポインターが解放済みであるかどうかのテストが追加されます。対してハードコードされた`delete
var;`文ではこのテストが省略されている点も実行時間の差分に含まれています。

まとめると **実行時の初期化コストの差**
でしょうか。この程度の差を気にしないのであれば、`delete`し忘れを防ぐ意味でも`unique`や`shared`を使うべきでしょう。また実行時間に対して初期化が支配的となるような場合、`new`と`unique`の差を気にするよりも構造を見直すべきです。

* * *

この他に、`make_unique()`を使う場合、`make_unique()`からコンストラクターを呼び出すことになりますので必然的にコンストラクターを`public`にする必要があります。

[code]

    class Test {
        Test() = default;
    public:
        static auto create() {
            return std::make_unique<Test>();    // error
        }
    };
    
[/code]

* * *

蛇足ですが、`malloc()`もメモリ確保だけを行い初期化しませんが、`calloc()`はゼロクリアします。


CC BY-SA 3.0
2017-04-03T02:02:54.877
33723
2017-04-03T02:02:54.877
4236
33665
answer


> C++11にはweak, shared, uniqueのスマートポインタがありますが、これらが使える環境であえてnew,
> deleteだけを使ったインスタンスの生成を使う意味はありますか?

[alphaさんコメント](https://ja.stackoverflow.com/questions/33665/#comment33105_33665)の通り`std::make_unique<T>`が追加されたC++14以降では、「スマートポインタで要求仕様を満たせるなら、new/deleteによるインスタンス生成/破棄を避けるべき」でしょう。

> C++11ではnew, deleteだけを使った方法は、使うべきではない方法という位置付けなのでしょうか?

どうしてもnew演算子を使わなければ解決できないケース（placement new,
alignment指定）を除いて、new/deleteを使うべきでないと考えます。

英語版SOの"[Are new and delete still useful in
C++14?](https://stackoverflow.com/questions/30762994/)"も参考にください。


CC BY-SA 3.0
2017-04-08T14:36:34.713
33872
2017-04-08T14:36:34.713
2017-05-23T12:38:55.307
-1
49
33665
answer


基本的にスマートポインタで統一すべきでしょう。  
ただ、コンストラクタがpublicでないクラスではmake_shared は簡単には使えません。

[code]

    class Foo {
    private:
        Foo();
    
    public:
        static std::shared_ptr<Foo> create() {
           return std::make_shared<Foo>();  // コンパイルエラー
        }
    };
    
[/code]

そのため

[code]

    std::shared_ptr<Foo>( new Foo() );
    
[/code]

と new を使うことになるかと思います。ただし、これを嫌ってテクニックを駆使してmake_shared を使う人も多いようです。


CC BY-SA 3.0
2017-07-25T15:47:45.937
36670
2017-07-25T17:13:54.193
2017-07-25T17:13:54.193
24517
24517
33665
answer