C ++での一時的な寿命を保証しますか?


103

C ++は、関数呼び出し内で作成され、パラメーターとして使用されない一時変数の存続期間を保証しますか?次にクラスの例を示します。

class StringBuffer
{
public:
    StringBuffer(std::string & str) : m_str(str)
    {
        m_buffer.push_back(0);
    }
    ~StringBuffer()
    {
        m_str = &m_buffer[0];
    }
    char * Size(int maxlength)
    {
        m_buffer.resize(maxlength + 1, 0);
        return &m_buffer[0];
    }
private:
    std::string & m_str;
    std::vector<char> m_buffer;
};

そして、これはあなたがそれをどのように使用するかです:

// this is from a crusty old API that can't be changed
void GetString(char * str, int maxlength);

std::string mystring;
GetString(StringBuffer(mystring).Size(MAXLEN), MAXLEN);

一時的なStringBufferオブジェクトのデストラクタはいつ呼び出されますか?それは...ですか:

  • GetStringを呼び出す前に?
  • GetStringが戻った後?
  • コンパイラ依存?

C ++は、参照が存在する限り、ローカルの一時変数が有効であることを保証していることを知っています。これは、メンバー変数への参照がある場合、親オブジェクトに適用されますか?

ありがとう。


継承してオーバーロードするか、グローバル関数を作成しませんか?メンバーを呼び出すためにクラスを作成する必要はありません。
JacekŁawrynowicz

1
これを使用する場合は、m_str.reserve(maxlength)in を呼び出す必要がありますchar * Size(int maxlength)。そうしないと、デストラクタがスローする可能性があります。
マンカルス2012年

回答:


109

この種の一時的なデストラクタは、完全式の最後に呼び出されます。これは、他の式の一部ではない最も外側の式です。これは、関数が戻って値が評価された後のケースです。だから、それはすべてうまくいきます。

それが実際に式テンプレートを機能させるものです。次のような式で、そのような一時的なものへの参照を保持し続けることができます。

e = a + b * c / d

すべての一時的なものは式まで続くので

x = y

完全に評価されています。それは非常に簡潔に説明されています12.2 Temporary objectsは、標準れています。


3
私は標準のコピーを手に入れるのにまったく取り掛かりませんでした。それを優先すべきです。
Mark Ransom、

2
@JohannesSchaub:この場合には、「フル・表現」とは何ですか:printf("%s", strdup(std::string("$$$").c_str()) );?私はあれば意味strdup(std::string("$$$").c_str())完全な表現として解釈され、その後、ポインタstrdup見ている有効。場合はstd::string("$$$").c_str()、完全な式があり、その後、ポインタstrdup見です無効!この例に基づいてもう少し説明してもらえますか?
Grim Fandango、2014

2
@GrimFandango AIUIあなたの全体printfが完全な表現です。したがって、これstrdupは不要なメモリリークですc_str()。直接印刷させるだけです。
Josh Stone

1
「そのような一時的なもの」-それはどのようなものですか?私の一時的なものが「その種の」一時的であるかどうかどうやって見分けることができますか?
RM

@RMは十分に公正です。質問で行われているように、「関数の引数内で作成するもの」を意味しました。
ヨハネスシャウブ-litb

18

litbの答えは正確です。一時オブジェクトの有効期間(右辺値とも呼ばれます)は式に関連付けられ、一時オブジェクトのデストラクタは式全体の最後に呼び出され、StringBufferのデストラクタが呼び出されると、m_bufferのデストラクタも呼び出されます。呼び出されますが、m_strは参照であるため、デストラクタではありません。

C ++ 0xは、右辺値参照を追加し、セマンティクスを移動するため、少しだけ変化することに注意してください。基本的に右辺値参照パラメーター(&&で表記)を使用することで、右辺値を(コピーする代わりに)関数に「移動」でき、右辺値の存続期間は、式ではなく、移動先のオブジェクトにバインドできます。MSVCチームからの非常に良いブログ投稿があり、これについて詳しく説明していますので、ぜひ読んでください。

右辺値を移動するための教育的な例は一時的な文字列であり、コンストラクターでの割り当てを示します。文字列メンバー変数を含むクラスMyTypeがある場合、次のようにコンストラクターで右辺値で初期化できます。

class MyType{
   const std::string m_name;
public:
   MyType(const std::string&& name):m_name(name){};
}

一時オブジェクトを使用してこのクラスのインスタンスを宣言すると、これは便利です。

void foo(){
    MyType instance("hello");
}

何が起こるかというと、一時オブジェクトのコピーと破棄を回避し、 "hello"を所有するクラスインスタンスのメンバー変数内に直接配置します。オブジェクトが「文字列」よりも重い場合、余分なコピーとデストラクタの呼び出しが重要になる可能性があります。


1
移動を機能させるには、constをドロップして、MyType(std :: string && name)のようにstd :: moveを使用する必要があると思います。m_name(std :: move(name)){}
gast128


3

StringBufferはGetStringのスコープ内にあります。GetStringのスコープの最後(つまり、戻り時)に破棄されます。また、C ++が参照がある限り、変数が存在することを保証するとは思いません。

以下はコンパイルすべきです:

Object* obj = new Object;
Object& ref = &(*obj);
delete obj;

私は保証を誇張したと思います-それは地元の一時的なものだけのためです。しかし、それは存在します。
Mark Ransom、

質問を編集しました。これまでに提供された回答に基づいて、しかし、それは議論の余地があるようです。
Mark Ransom、

私はまだあなたの編集が正しいとは思いません:Object&obj = GetObj(); Object&GetObj(){return&Object(); } //悪い-ぶら下がっている参照を残します。
BigSandwich、2009

1
私は明らかに自分自身を説明するのに悪い仕事をしています、そして私も100%理解していないかもしれません。informit.com/guides/content.aspx?g=cplusplus&seqNum=198をご覧ください。元の質問についても説明し、回答しています。
Mark Ransom、

1
リンクありがとうございます。
BigSandwich、2009

3

私はほぼ同じクラスを書きました:

template <class C>
class _StringBuffer
{
    typename std::basic_string<C> &m_str;
    typename std::vector<C> m_buffer;

public:
    _StringBuffer(std::basic_string<C> &str, size_t nSize)
        : m_str(str), m_buffer(nSize + 1) { get()[nSize] = (C)0; }

    ~_StringBuffer()
        { commit(); }

    C *get()
        { return &(m_buffer[0]); }

    operator C *()
        { return get(); }

    void commit()
    {
        if (m_buffer.size() != 0)
        {
            size_t l = std::char_traits<C>::length(get());
            m_str.assign(get(), l);    
            m_buffer.resize(0);
        }
    }

    void abort()
        { m_buffer.resize(0); }
};

template <class C>
inline _StringBuffer<C> StringBuffer(typename std::basic_string<C> &str, size_t nSize)
    { return _StringBuffer<C>(str, nSize); }

標準以前は、コンパイラごとに異なる方法で行われていました。C ++の古い注釈付きリファレンスマニュアルでは、一時範囲はスコープの最後でクリーンアップする必要があると指定されていたので、一部のコンパイラはそれを行いました。2003年には、SunのForte C ++コンパイラでもデフォルトで動作が存在するため、StringBufferは機能しませんでした。しかし、現在のコンパイラがそれでも壊れていたら、私は驚かれることでしょう。


彼らはどれほど不気味です!警告をありがとう-私が最初に試す場所はVC ++ 6であり、その標準への準拠は知られていない。注意深く見守ります。
Mark Ransom、

私はもともとVC ++ 6でクラスを作成したので、問題にはなりません。
Daniel Earwicker、2009
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.