コピーコンストラクタについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
小太鼓

コピーコンストラクタについて

#1

投稿記事 by 小太鼓 » 15年前

はじめまして,小太鼓と申します.

C++の質問してもいいですよね・・・・?

ちょっとした疑問なんですけど,

以下のコードで,パターン1,2の結果が得られました.
#対応するコードはコメントアウトしてあります.

パターン1ではoperator=関数が呼び出されていますが,
パターン2では呼び出されていません.
それなのに,tttを出力すると期待通りの値が入力されています.
operator=が呼び出されてないならコピーコンストラクタかなと思ったんですが,
コピーコンストラクタはtttに関して呼び出されていません.

パターン2でtttに値がいつ入力されたのかわからない気持ち悪さが残っています.

ご協力願います.

パターン1
argument : Hello 0xbfdf6f98
argument : World 0xbfdf6f90
copy[Hello] 0xbfdf6f88
default[nul[/url] 0xbfdf6f84
--operator--
argument : foobar 0xbfdf6f80
0xbfdf6f98+0xbfdf6f90
/--operator--
operator= HelloWorld
destruct[HelloWorld] 0xbfdf6f80
output HelloWorld 0xbfdf6f84
destruct[HelloWorld] 0xbfdf6f84
destruct[Hello] 0xbfdf6f88
destruct[World] 0xbfdf6f90
destruct[Hello] 0xbfdf6f98

パターン2
argument : Hello 0xbf89cad8
argument : World 0xbf89cad0
copy[Hello] 0xbf89cac8
--operator--
argument : foobar 0xbf89cac4
0xbf89cad8+0xbf89cad0
/--operator--
output HelloWorld 0xbf89cac4
destruct[HelloWorld] 0xbf89cac4
destruct[Hello] 0xbf89cac8
destruct[World] 0xbf89cad0
destruct[Hello] 0xbf89cad8

#include<iostream>
#include<string>

class Test {

  public:
    Test() : str("null") {std::cout << "default[" << str << "] " << this << std::endl;}
    Test(std::string _str) : str(_str) {std::cout << "argument : " << str << " " << this << std::endl;}
    ~Test() {std::cout << "destruct[" << str << "] " << this << std::endl;}
    Test(const Test& t) {str = t.string(); std::cout << "copy[" << str << "] " << this << std::endl;}

    std::string string() const {return str;}
    void set(std::string _str) {str = _str;}

    void operator=(Test t) {

      str = t.string();
      std::cout << "operator= " << t.string() << std::endl;
    }

  private:
    std::string str;
};

Test operator+(Test& str_a, Test& str_b) {

  std::cout << "--operator--" << std::endl;

  Test t("foobar");

  std::cout << &str_a << "+" << &str_b << std::endl;

  t.set(str_a.string() + str_b.string());

  std::cout << "/--operator--" << std::endl;

  return t;
}

int main(int argc, char const* argv[/url]) {

  Test t("Hello"), tt("World");
  Test t2 = t;
//  パターン1
//  Test ttt;
//
//  ttt = t + tt;
//  パターン1終

//  パターン2
//  Test ttt = t + tt;
//  パターン2終

  std::cout << "output " << ttt.string() << " " << &ttt << std::endl;

  return 0;
}

Justy

Re:コピーコンストラクタについて

#2

投稿記事 by Justy » 15年前

 RVO(戻値の最適化)ですね。しかも名前付きの。
 それによって operator+()の中で生成した tがそのまま呼び出し元の tttとして使われているのでしょう。 

 
Return value and constructor
http://www.fides.dti.ne.jp/~oka-t/cppla ... -ctor.html

RVOによるコピーコンストラクタの最適化 - (void*)Pないと
http://d.hatena.ne.jp/pknight/20090818/1250567769

行列ライブラリの設計(2) 12 一時オブジェクト(2)
- 3.12.5 戻り値最適化
http://www.asahi-net.or.jp/~uc3k-ymd/Le ... 03_12.html

小太鼓

Re:コピーコンストラクタについて

#3

投稿記事 by 小太鼓 » 15年前

Justyさま,

返信ありがとうございます.

URLじっくり拝見したいと思います.

小太鼓

Re:コピーコンストラクタについて

#4

投稿記事 by 小太鼓 » 15年前

では,この場合はNRVOということになるのでしょうか?

RVOとNRVOの違いは名前の付いたオブジェクトを返した場合だと書いてあったんですが,
なにをもって名前つきと言ってるのでしょうか?

初心者なんでアホな質問ですが・・・

よろしくおねがいします.

Justy

Re:コピーコンストラクタについて

#5

投稿記事 by Justy » 15年前

>この場合はNRVOということになるのでしょうか
 そうですね。

>なにをもって名前つきと言ってるのでしょうか?
 変数‘名’があるかどうか、です。
 直接コンストラクタを呼び出した一時オブジェクトを戻り値にすると名前なしになります。


NRVO(RVO)とMove Semantics - 神様なんて信じない僕らのために
http://d.hatena.ne.jp/Isoparametric/20091219/1261192152

YuO

Re:コピーコンストラクタについて

#6

投稿記事 by YuO » 15年前

・operator=がパターン2で呼ばれない点について
宣言文中の,declaratorに続く=はAssignment operatorsの=ではなく,initializerの=なので,初期化に使われます。
なので,
> Test ttt = t + tt;

Test ttt(t + tt);
は同等です。
ref) ISO/IEC 14882:2003 (以下IS) 8 Declarators / 8.5 Initializers
ref) IS 12 Special member functions / 12.6 Initialization / 12.6.1 Explicit initialization

なので,RVO云々を抜きにして,operator=は呼ばれません。

・RVOについて
一応,規格上の話を。
ref) IS 12.8 Copying class objects / Paragraph. 15
> When certain criteria are met, an implementation is allowed to omit the copy construction of a class object, even if the copy constructor and/or destructor for the object have side effects.
(略)
> - in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy operation can be omitted by constructing the automatic object directly into the function's return value
> - when a temporary class object that has not been bound to a reference would be copied to a class object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary object directly into the target of the omitted copy
最初の条件で,operator=内のtのreturnによるコピーが抑制され,さらに後の条件でoperator=の内のtの構築がtttの構築に置き換わります。
パターン1でも,最初の条件は使われていることに注意してください。
また,この省略は,副作用の有無にかかわらず実行されます。コピーコンストラクタやoperator=で副作用のあることをやろうとすると,すっ飛ばされることがあるので注意してください。

なお,規格上はRVOとNRVOの違いはありません。
実装側がどこまで対応しているかの違いです。

ookami

Re:コピーコンストラクタについて

#7

投稿記事 by ookami » 15年前

RVOとかNRVOって初めて知りました…; いつも勉強になります。

以下、直接の回答にはなりませんが… 私の場合は、コピーコンストラクタとoperator=について理解が及ばず、以下のようにして封印しています。理解していない物は使わない方針で。

class T{
private:
なにがし
public:
なにがし
T(T& obj);
void operator=(T& obj);
};

それぞれ宣言だけして実装をかかないことで、うっかり使おうとしてもリンカがはじいてくれます。

…っていうやり方はまずいですかね。「それにも罠が」っていう事があったらご指摘ください。

たかぎ

Re:コピーコンストラクタについて

#8

投稿記事 by たかぎ » 15年前

> うっかり使おうとしてもリンカがはじいてくれます。

これはダメでしょう。
コピーを禁止するなら、コピーコンストラクタとコピー代入演算子をprivateにしないと。

ookami

Re:コピーコンストラクタについて

#9

投稿記事 by ookami » 15年前

またやっちまったかw と思いましたが、ちゃんとリンカがはじいてくれました。



class T {
private:
int i;
public:
int j;
T(){}
~T(){}
T(T &obj); // コピーコンストラクタの宣言だけして実装を書かない
void operator=(T &obj); // operator=の宣言だけして実装を書かない
};

void main(void) {
T t1;

// コピーコンストラクタ
T t2(t1);

// operator=
T t3;
t3=t1;
}

コンパイルしています...
main.cpp
リンクしています...
main.obj : error LNK2001: 外部シンボル ""public: void __thiscall T::operator=(class T &)" (??4T@@QAEXAAV0@@Z)" は未解決です。
main.obj : error LNK2001: 外部シンボル ""public: __thiscall T::T(class T &)" (??0T@@QAE@AAV0@@Z)" は未解決です。
c:\ookami\programming\test\Debug\test.exe : fatal error LNK1120: 外部参照 2 が未解決です。
ビルドログは "file://c:\ookami\programming\test\Debug\BuildLog.htm" に保存されました。
test - エラー 3、警告 0



↑コンパイルは通り、リンクで失敗しています。
たかぎさんがおっしゃるパターンでは、コンパイルの段階ではじいてくれました。



class T {
private:
int i;
T(T &obj); // コピーコンストラクタの宣言だけして実装を書かない
void operator=(T &obj); // operator=の宣言だけして実装を書かない
public:
int j;
T(){}
~T(){}
};

void main(void) {
T t1;

// コピーコンストラクタ
T t2(t1);

// operator=
T t3;
t3=t1;
}

コンパイルしています...
main.cpp
c:\ookami\programming\test\main.cpp(18) : error C2248: 'T::T' : private メンバ (クラス 'T' で宣言されている) にアクセスできません。
c:\ookami\programming\test\main.cpp(6) : 'T::T' の宣言を確認してください。
c:\ookami\programming\test\main.cpp(3) : 'T' の宣言を確認してください。
c:\ookami\programming\test\main.cpp(22) : error C2248: 'T::operator =' : private メンバ (クラス 'T' で宣言されている) にアクセスできません。
c:\ookami\programming\test\main.cpp(7) : 'T::operator =' の宣言を確認してください。
c:\ookami\programming\test\main.cpp(3) : 'T' の宣言を確認してください。
ビルドログは "file://c:\ookami\programming\test\Debug\BuildLog.htm" に保存されました。
test - エラー 2、警告 0


あと、当然ですが、宣言もかかなければ、
勝手に生成されるコピーコンストラクタとoperator=が使われるので、
コンパイルもリンクも通りますね。
でもこれでは意図しない動作が(特にポインタをメンバに持っていると)起こるため、
まずご法度ですと。

class T {
private:
int i;
public:
int j;
T(){}
~T(){}
};

void main(void) {
T t1;

// コピーコンストラクタ
T t2(t1);

// operator=
T t3;
t3=t1;
}

コンパイルしています...
main.cpp
リンクしています...
マニフェストを埋め込んでいます...
ビルドログは "file://c:\ookami\programming\test\Debug\BuildLog.htm" に保存されました。
test - エラー 0、警告 0

たかぎ

Re:コピーコンストラクタについて

#10

投稿記事 by たかぎ » 15年前

> ↑コンパイルは通り、リンクで失敗しています。
> たかぎさんがおっしゃるパターンでは、コンパイルの段階ではじいてくれました。

sizeof演算子のオペランドにした場合など、リンカでエラーを検出できない場合があります。
また、仮に確実にリンクエラーにできるとしても、エラー検出は可能な限り前の段階で行うべきで、今回であればコンパイルエラーにできるわけですから、そうすべきです。

ookami

Re:コピーコンストラクタについて

#11

投稿記事 by ookami » 15年前

> エラー検出は可能な限り前の段階で行うべきで、今回であればコンパイルエラーにできるわけですから、そうすべきです

確かに、私は「エラー検出は可能な限り前の段階で行う」のを軽視して、

class T {
private:
int i;
T(T &obj);
void operator=(T &obj);
public:
int j;
T(){}
~T(){}
};

より


class T {
private:
int i;
public:
int j;
T(){}
~T(){}
T(T &obj);
void operator=(T &obj);
};

のほうが何となく好み、というだけでこっちを採用していました。
(コンストラクタ、デストラクタ、コピーコンストラクタ、コピー代入演算子が並ぶので)

「エラー検出は可能な限り前の段階で行うべき」なのは分かりますので検討してみます。
というかすでにその気になってきましたw

小太鼓

Re:コピーコンストラクタについて

#12

投稿記事 by 小太鼓 » 15年前

研究室が忙しくてあんまり見れてませんでした,すみません

疑問が解決してすっきりしましたし,他のユーザのみなさんも議論できる場が出来てうれしいです.


RVOの他にもこれは知っとくべきだというテクニックはあるんでしょうか?

ISLe

Re:コピーコンストラクタについて

#13

投稿記事 by ISLe » 15年前

コンパイルエラーだと問題のあるコードにタグジャンプできますし。

閉鎖

“C言語何でも質問掲示板” へ戻る