質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

3回答

21777閲覧

C#にコピーコンストラクタがないのはなぜ?

aridai1221

総合スコア45

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

1クリップ

投稿2016/05/14 09:52

編集2016/05/14 11:14

C#初心者です。
最近疑問に思ったことなのですが、
C#にはなぜコピーコンストラクタが存在しないのでしょうか。

C++では
参照型のデータの整合性を保つため、
メモリの多重解放を防ぐために
コピーコンストラクタで対策をすると思います。
(参考にさせてもらったページ)

C#では
プログラマ自身がメモリの解放処理を書くことは無いのですが、
データの整合性を保つためという点においては
コピーコンストラクタがあれば便利だと思います。

以下は考えるのに使ったコードです。

C#

1using System; 2 3public class Program 4{ 5 public static void Main() 6 { 7 // 構造体(値型)のインスタンスを生成する 8 var hoge = new Hoge { foo = new Foo(100) }; 9 10 // このままの状態で値を確認してみる 11 Console.WriteLine(hoge.foo.x); 12 13 // 関数に値渡しをしてから 14 // 値を変更させてみる 15 func(hoge); 16 17 // 値をもう一度確認してみる 18 Console.WriteLine(hoge.foo.x); 19 } 20 21 public static void func(Hoge hoge) 22 { 23 // コピーコンストラクタが呼ばれて 24 // hoge.foo = new foo(); のような処理によって 25 // hoge.fooのメモリを新しく確保させて 26 // 渡された元のメンバには 27 // なんの影響も与えさせないようにさせたいな… 28 // でもコピーコンストラクタが言語仕様に存在しないな… 29 30 // 渡された元のメンバと 31 // 同じものを参照しているから 32 // ここで値を書き換えると 33 // 元の方にも影響が出てしまう… 34 hoge.foo.x = 200; 35 Console.WriteLine(hoge.foo.x); 36 37 // ここで仮引数のhogeの寿命は終わってしまう 38 // もしC++なら 39 // newで確保したメモリはdeleteで解放する必要がある 40 // もしデストラクタでメモリの解放処理を行っているのであれば 41 // hogeの寿命が終わるのでメモリの解放処理が呼ばれると思う 42 // もし、hoge.fooのメモリをここで解放してしまうと 43 // 元のhoge.fooが指しているものもメモリ解放されてしまう… 44 // 幸いこのコードはC#で書かれているため 45 // そんなことは起こらないのだが… 46 } 47} 48 49// 値型 50public struct Hoge 51{ 52 // 参照型のメンバを持つ 53 public Foo foo; 54} 55 56// 参照型 57public class Foo 58{ 59 public int x; 60 public Foo(int x) { this.x = x; } 61}

出力結果はこうなりました。

100 200 200

やはり渡された元のインスタンスのメンバに
影響を与えているようです。

このようなことを防ぐために
Clone()メソッドのようなものを実装するという方法がありますが、
これは言語仕様で強制的に使わないといけないというものではないため
プログラマが忘れればやはり
あのようなバグの原因になるうるコードになります。

このようなことから
私はC#にコピーコンストラクタが存在していても良いと思ったのですが、
なぜ言語仕様に存在しないのでしょうか。

初心者すぎる質問ですいません…

追記
構造体のメンバにクラスを含める事自体が根本的に間違っているというか
状況としては少ないということに気づきました。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

私見ですが、言語設計の思想によるものと思います。

C++の基であるCは原則値渡しであり、全ては値のコピーです。構造体も値渡しであり各メンバーのコピーです。C++のクラスはCの構造体の拡張であるため、基本は値渡しであり、各メンバーのコピーです。しかし、単純なコピーでは不整合が起きる場合があるため、コピーのための処理が追加できるようにコピーコンストラクタが作られました。デフォルトコピーコンストラクタが単純なコピーであるのもその影響です。

しかし、その後のGCがあるオブジェクト指向言語(特にJava)では、オブジェクト全体をコピーをするという処理を言語の基本に置かないようにしました。変数はオブジェクトへの参照にし、参照値(Javaの場合の用語、他の言語では違う用語になるか、実装上はただのポインタの場合が多い)のみをコピーすることを基本にしました。つまり、C++風に言えば、全体をコピーするのではなく、ポインタをコピーすることを基本にしたのです。ポインタのコピーを基本にした場合、いつメモリ解放できるのかの問題が出てきます。これを補うためにGCを必須とし、言語仕様に組み込んだのです。

C#はJavaの言語設計に大きく影響を受けた言語です(ただし、C#の直接の祖先はDelphiです)。クラスが定義するオブジェクトは基本的に参照型とし、同じように参照値のみのコピーとしました。各フィールドをコピーすることは処理が重く、無駄であると判断したのです。ただ、フィールド全ての値をコピーした場合もいい事があるため、Javaより一工夫を入れ、値型として構造体を別途用意しました(C++ではclassとstructは実質同じなのに対し、C#ではclassとstructは大きく異なります)。もし、値を全てコピーするような使い方をしたかったら構造体を使うようにしたのです。しかし、そのような場合は限られていおり、また、実装上の問題もあるため、構造体はクラスに比べて制限を設けるようにしたのです。

なぜ、このような流れになったのでしょうか。理由は単純です。コピーコンストラクタが遅いからです。

コピーコンストラクタはコストが高い処理です。高速なC++であっても巨大なvectorやstringに対してコピーコンストラクタを走らせた日には泣きたくなるぐらい遅いです。C++ができる人ほどコピーコンストラクタを避ける傾向にあります。プリミティブ型などを除き、関数への引数はポインタ値渡しか参照渡しを使います。どうしても値を返さなければならないときは右辺値参照になるよう工夫をしてムーブコンストラクタを使います。C++11以降はスマートポインタやムーブコンストラクタが使いやすくなったため、コピーコンストラクタが使う傾向はますます減ってきていると思います。

最後に、コピーコンストラクタで無ければ呼び出し先で影響があるのでは?という話ですが、それを避けるために、最近の流行が関数型プログラミングを模倣した設計です。どういうことかというと、

  • オブジェクトを変更不可(immutable)にする。
  • 参照透過な関数のみにする。(参照透過性とは副作用が一切無く、引数が同じなら常に同じ値を返すという性質です)

ということです。上を完全に守ろうとするとモナドやカリー化と言った関数型言語特有脳機能がないと厳しいですが、部分的に取り入れることは他のどのような言語でも可能です。変更不可と参照透過性が守られていれば、オブジェクトを参照の値渡しで関数に渡しても勝手に変えられたという問題が無くなります。そのとき、わざわざ処理が重いコピーコンストラクタを使おうと思う人は誰もいなくなると言うことです。メモリ解放問題についてはGC頼りですが、C++でもスマートポインタを使えばある程度緩和はできますので、こちらも問題にする人はいなくなると思います。

※ Cは上の話とは別です。Cはメモリの全ての処理を完全に手動で把握したい人が使う言語です。

投稿2016/05/14 11:53

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

C#のクラスは「参照型」と良くいわれますが、実際にはC++の「ポインタ」のように振る舞います。
例えば、C++の構造体にポインタ型のメンバ変数があったとして、その構造体のオブジェクトをコピーすると、当然ポインタもコピーされますが、コピーされたポインタはコピー元のポインタと同じインスタンスを参照していますよね。それと同じ原理です。
したがって、ご質問にあるコードと同様のコードをC++で作ると、結果も同様になります。

データの整合性を保つためという点においては
コピーコンストラクタがあれば便利だと思います。

コピーコンストラクタはC#でも作れます。
方法 : コピー コンストラクターを記述する (C# プログラミング ガイド)

ただし、コピーコンストラクタはデータの整合性を保証するためのものではありません。データの整合性を保証することは設計者の責務です。メソッドに渡したオブジェクトが変更されないようにしたいなら、そのようなコードを書かなくてはなりません。もしメソッドに渡したオブジェクトが変更されるなら、その旨をコメントなり仕様書なりに明記しなくてはいけません。

投稿2016/05/14 11:17

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

クラスはnewをしたときだけインスタンスがつくられるとおぼえておけばいいと思いますよ(たぶん

値型のstructは一時的に使ったりパフォーマンスを求めたり、dllとのやりとり。

structの中にclassを持たせるのはあまり見ないですね。コードの通り、値渡しの時classは参照を渡すだけですので。

何故かというと……なんでだっけかな?^^;
ただ、参考ページのやり方ですと

Kitty obj = g_obj;

これ、実際にKittyクラスのコンストラクタを見に行かないと何をしているかわからないんですよ。

プログラマが忘れればやはり
あのようなバグの原因になるうるコードになります。

むしろC++のコピーコンストラクタのほうがバグの原因になりがちかもしれません。C#になれたら違和感なくかけると思われますよー

投稿2016/05/14 11:07

TomoakiKurata

総合スコア46

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aridai1221

2016/05/14 11:09

> structの中にclassを持たせるのはあまり見ないですね。コードの通り、値渡しの時classは参照を渡すだけですので。 確かに言われてみればそうですね。 私自身も構造体にクラスのインスタンスを持たせる実装はあまりないです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問