newなしでc ++でコンストラクターを呼び出す


142

私は人々がC ++でオブジェクトを作成することをよく見ました

Thing myThing("asdf");

これの代わりに:

Thing myThing = Thing("asdf");

少なくともテンプレートが含まれていない限り、これは(gccを使用して)機能するようです。私の質問は、最初の行が正しいのですか、そうであればそれを使用する必要がありますか?


25
どちらの形も新品なしです。
ダニエルダラナス

13
2番目の形式はコピーコンストラクターを使用するため、同等ではありません。
エドワード・ストレンジ

私はそれを少しを果たした、第一の方法は...テンプレートは、パラメータなしのコンストラクタで使用されている場合、時には失敗するようだ
ニルス

1
ああ、私はそのための「素敵な質問」バッジを得ました、なんて残念!
Nils

回答:


153

両方の行は実際には正しいですが、微妙に異なることを行います。

最初の行は、フォーマットのコンストラクターを呼び出すことにより、スタック上に新しいオブジェクトを作成しますThing(const char*)

2つ目はもう少し複雑です。基本的には次のことを行います

  1. Thingコンストラクターを使用してタイプのオブジェクトを作成するThing(const char*)
  2. Thingコンストラクターを使用してタイプのオブジェクトを作成するThing(const Thing&)
  3. ~Thing()手順1で作成したオブジェクトを呼び出す

7
これらのタイプのアクションは最適化されているため、パフォーマンスの面で大きな違いはないと思います。
M.ウィリアムズ

14
あなたのステップは完全に正しいとは思いません。Thing myThing = Thing(...)代入演算子を使用せずThing myThing(Thing(...))、と同じようにコピー構築され、デフォルト構築は含まれませんThing(編集:投稿はその後修正されました)
AshleysBrain

1
したがって、2行目は明確な理由もなくリソースを浪費しているため、2行目が間違っていると言えます。もちろん、最初のインスタンスの作成が一部の副作用のために意図的である可能性はありますが、それは(スタイリスト的に)さらに悪いことです。
MK。

3
いいえ、@ Jaredは保証されません。ただし、コンパイラーがその最適化を実行することを選択した場合でも、コピーコンストラクターは、実装または呼び出されていなくても、アクセス可能(つまり、保護またはプライベートではない)である必要があります。
ロブ・ケネディ

3
コピーはコピーコンストラクタが副作用を持っている場合でも省略さすることができます表示されます-私の答えを参照してください。stackoverflow.com/questions/2722879/...
ダグラスLeeder

31

私はあなたが実際に意味する2行目で仮定します:

Thing *thing = new Thing("uiae");

これは、新しい動的オブジェクト(動的バインディングとポリモーフィズムに必要)を作成し、それらのアドレスをポインターに格納する標準的な方法です。あなたのコードは、(1が渡され、すなわち2つのオブジェクトを作成し、JaredParは説明何をconst char*、他の合格const Thing&)した後、(デストラクタを呼び出して~Thing()最初のオブジェクト(上)const char*1)。

対照的に、これ:

Thing thing("uiae");

現在のスコープを終了すると自動的に破棄される静的オブジェクトを作成します。


1
残念ながら、auto_ptr、unique_ptrなどを使用する代わりに、これが新しい動的オブジェクトを作成する最も一般的な方法です。
Fred Nurk、2011年

3
OPの質問は正しかった。この回答は別の問題に完全に関係している(@JaredParの回答を参照)
Silmathoron

21

コンパイラーは2番目の形式を最初の形式に最適化することもできますが、そうする必要はありません。

#include <iostream>

class A
{
    public:
        A() { std::cerr << "Empty constructor" << std::endl; }
        A(const A&) { std::cerr << "Copy constructor" << std::endl; }
        A(const char* str) { std::cerr << "char constructor: " << str << std::endl; }
        ~A() { std::cerr << "destructor" << std::endl; }
};

void direct()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a(__FUNCTION__);
    static_cast<void>(a); // avoid warnings about unused variables
}

void assignment()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a = A(__FUNCTION__);
    static_cast<void>(a); // avoid warnings about unused variables
}

void prove_copy_constructor_is_called()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a(__FUNCTION__);
    A b = a;
    static_cast<void>(b); // avoid warnings about unused variables
}

int main()
{
    direct();
    assignment();
    prove_copy_constructor_is_called();
    return 0;
}

gcc 4.4からの出力:

TEST: direct
char constructor: direct
destructor

TEST: assignment
char constructor: assignment
destructor

TEST: prove_copy_constructor_is_called
char constructor: prove_copy_constructor_is_called
Copy constructor
destructor
destructor

静的キャストを無効にする目的は何ですか?
スティーブンクロス

1
@Stephen未使用の変数に関する警告を避けます。
ダグラスリーダー2010

10

簡単に言うと、両方の行が「new」のようにヒープ上ではなくスタック上にオブジェクトを作成します。2行目は実際にはコピーコンストラクターへの2番目の呼び出しを含むため、回避する必要があります(コメントに示されているように修正する必要もあります)。小さいオブジェクトにはできるだけ高速でスタックを使用する必要がありますが、オブジェクトがスタックフレームよりも長く存続する場合は、明らかに間違った選択です。


ヒープではなくスタック上でオブジェクトをインスタンス化することの違い(つまり、newを使用していてnewを使用していない)に不慣れな方のために、ここに良いスレッドがあります。
edmqkk

2

理想的には、コンパイラーが2番目を最適化しますが、必須ではありません。最初が最善の方法です。ただし、C ++でのスタックとヒープの違いを理解することは非常に重要です。自分のヒープメモリを管理する必要があるためです。


コンパイラーは、コピーコンストラクターに副作用(I / Oなど)がないことを保証できますか?
スティーブンクロス

@Stephen -それは問題でコピーコンストラクタは、I / Oを行う場合はありません-私の答えを参照stackoverflow.com/questions/2722879/...を
ダグラスLeeder

はい、わかりました。コンパイラは2番目の形式を最初の形式に変換できるため、コピーコンストラクタの呼び出しを回避できます。
スティーブンクロス

2

私は少し遊んでみましたが、コンストラクターが引数を取らない場合、構文がかなりおかしくなったようです。例を挙げましょう。

#include <iostream> 

using namespace std;

class Thing
{
public:
    Thing();
};

Thing::Thing()
{
    cout << "Hi" << endl;
}

int main()
{
    //Thing myThing(); // Does not work
    Thing myThing; // Works

}

つまり、Thing myThingをブラケットなしで記述するだけで実際にコンストラクターが呼び出され、Thing myThing()はコンパイラーに関数ポインターなどを作成させます?? !!


6
これは、C ++でよく知られている構文のあいまいさです。"int rand()"と書いた場合、コンパイラは、 "intを作成してデフォルトで初期化する"のか、 "関数のrandを宣言する"のかを判断できません。ルールは、可能な限り後者を選択することです。
jpalecek 2010

1
そして、これは、最も厄介な構文解析です。
Marc.2377 2018年

2

JaredParの回答に追加

1-通常のctor、2番目の関数のようなctor、一時オブジェクト。

このソースをここhttp://melpon.org/wandbox/のどこかに異なるコンパイラでコンパイルします

// turn off rvo for clang, gcc with '-fno-elide-constructors'

#include <stdio.h>
class Thing {
public:
    Thing(const char*){puts(__FUNCTION__ );}
    Thing(const Thing&){puts(__FUNCTION__ );}   
    ~Thing(){puts(__FUNCTION__);}
};
int main(int /*argc*/, const char** /*argv*/) {
    Thing myThing = Thing("asdf");
}

そして、結果が表示されます。

ISO / IEC 14882 2003-10-15から

8.5、パート12

1番目、2番目の構造は直接初期化と呼ばれます

12.1、パート13

関数表記の型変換(5.2.3)を使用して、その型の新しいオブジェクトを作成できます。[注:構文は、コンストラクターの明示的な呼び出しのように見えます。] ...この方法で作成されたオブジェクトには名前がありません。[注:12.2は一時オブジェクトの存続時間を説明しています。] [注:明示的なコンストラクター呼び出しは左辺値を生成しません。3.10を参照してください。]


RVOについて読む場所:

12特別なメンバー関数/ 12.8クラスオブジェクトのコピー/パート15

特定の基準が満たされている場合、オブジェクトのコピーコンストラクターやデストラクタに副作用がある場合でも、実装はクラスオブジェクトのコピー構築を省略できます。

そのようなコピー動作を表示するには、コメントからコンパイラフラグを使用してオフにしてください)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.