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

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

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

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

Q&A

4回答

5646閲覧

委譲とオーバライドの違い

saito.kaz

総合スコア76

C++

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

0グッド

1クリップ

投稿2016/01/25 10:48

Worker.cpp で Phoneクラスのオブジェクトをコンポジションとして持っています。
その後、WorkerクラスをSalesクラスで継承しています。

Workerクラスでは、Phoneコンストラクタでメンバイニシャライザをしており、Salesクラスでイニシャライザを行っています。

###前提・実現したいこと
Phoneクラスを仮想関数(Virtual ShowData())にして、Workerクラスでその関数を実装する場合( ShowData())、通常のオーバーライドとどう違うのでしょうか。
皆様は、具体的にどう使い分けているのでしょうか。

###ソースコード

<Sales.h> #include <string.h> #include "Worker.h" class Sales:public Worker{ public: double profit; char* client; std::string addtionalInfo; Sales(); Sales(double profit, char* client, std::string addtionalInfo, int number, char* name, double salary, bool i, char* plan, std::string pInfo); Sales(const Sales &obj); ~Sales(); void ShowData(Sales s1); }; <Sales.cpp> #include <iostream> #include <string.h> #include "Sales.h" using namespace std; Sales::Sales(double profit, char* client, std::string addtionalInfo, int number, char* name, double salary, bool i, char* plan, std::string pInfo):Worker(number, name, salary, i, plan, pInfo){ this->profit = profit; this->client = new char[100]; strcpy(this->client, client); this->addtionalInfo = addtionalInfo; } Sales::Sales(const Sales &obj){ client = new char[100]; strcpy(client,obj.name); this->profit =obj.profit; this->addtionalInfo=obj.addtionalInfo; } Sales::~Sales(){ } Sales::Sales():Worker(){ this->profit = 100; this->client = new char[100]; strcpy(this->client, "Undifined_client"); this->addtionalInfo="undifined_additionalInfo"; } void Sales::ShowData(Sales s1){ cout << "s1.name = " << s1.name << "\n"; cout << "s1.pInfo = " << s1.phone.pInfo << "\n"; cout << "s1.profit = " << s1.profit << "\n"; cout << "s1.client = " << s1.client << "\n"; cout << " s1.addtionalInfo = " << s1.addtionalInfo << "\n"; cout << " s1.addtionalInfo " << s1.addtionalInfo << "\n"; } int main(){ Sales s1; return 0; } <Worker.h> #include <string.h> #include "Phone.h" class Worker{ public: int number; char* name; double salary; Phone phone; Worker(); Worker(int number, char* name, double salary, bool i, char* plan, std::string pInfo); Worker(const Worker &obj); ~Worker(); }; <Worker.cpp> #include <iostream> #include "Worker.h" using namespace std; Worker::Worker(){ name = new char[80]; strcpy(name, "undifined in Worker con"); number = 0; salary = 0; } Worker::Worker(int number, char* name, double salary,bool i, char* plan, string pInfo):phone(i ,plan ,pInfo){ cout<< " This is Constructor called Worker(int number, char* name, double salary):Phone(bool i, char* plan, string pInfo) " << "\n"; this->number = number; this->name = new char[80]; strcpy(this->name, name); this->salary = salary; } Worker::Worker(const Worker &obj){ name = new char[80]; strcpy(name,obj.name); this->number =obj.number; this->salary = obj.salary; }; Worker::~Worker(){ delete[] name; } /* int main(){ Worker w1; strcpy(w1.name,"Takayuki"); //Worker::Worker(int number, char* name, double salary,bool i, char* plan, string pInfo): w1.number =10; w1.salary = 200; ShowData(w1); //Worker::Worker(int number, char* name, double salary,bool i, char* plan, string pInfo): Worker w2(1000, "name_w2", 1000, false, "plan_w2", "pInfo_w2"); ShowData(w2); return 0; } */ <Worker.h> #include <string.h> #include "Phone.h" class Worker{ public: int number; char* name; double salary; Phone phone; Worker(); Worker(int number, char* name, double salary, bool i, char* plan, std::string pInfo); Worker(const Worker &obj); ~Worker(); }; <Phone.cpp> #include <iostream> #include <string.h> #include "Phone.h" using namespace std; Phone::Phone(){ plan = new char[100]; this->i = false; strcpy(this->plan, "normal"); this->pInfo = "nokia"; } Phone::Phone(bool i, char* plan, string pInfo){ this->plan = new char[100]; this->i = i; strcpy(this->plan, plan); this->pInfo = "nokia"; } Phone::~Phone(){ delete[] plan; } void Phone::ShowPhone(){ cout << " i = " << this->i << "\n"; cout << " plan = " << this->plan <<"\n"; cout << " pInfo = " << this->pInfo <<"\n"; } void Phone::ShowData(){ } /* int main(){ Phone p1; Phone p2(false, "p2","noraml2"); Phone p3(false, "p3","noraml3"); cout << "----------p2.ShowPhone();-------------------" << "\n"; p2.ShowPhone(); cout << "----------p3.ShowPhone();-------------------" << "\n"; p3.ShowPhone(); cout << "-----------------------------" << "\n"; p1.i = false; strcpy(p1.plan,"normal"); p1.pInfo = "nokia"; p1.ShowPhone(); cout << "-----------------------------" << "\n"; p2.ShowData(); return 0; } */ <Phone.h> #include <string.h> class Phone{ public: Phone(); Phone(bool i, char* plan, std::string pInfo); ~Phone(); bool i; char* plan; std::string pInfo; void ShowPhone(); virtual void ShowData(); };

###補足情報(言語/FW/ツール等のバージョンなど)
Paiza.io

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

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

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

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

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

guest

回答4

0

こんにちは。

Phoneクラスを仮想関数(Virtual ShowData())にして、Workerクラスでその関数を実装する場合( ShowData())、通常のオーバーライドとどう違うのでしょうか。

えっと、WorkerのShowData()がPhone::ShowData()を呼ぶ場合、これをvirtualにしても意味無いです。ポリモーフィズムする(基底クラスへのポインタ経由で派生クラスのメンバ関数を呼び出す)時にvirtual指定するものですから。

また、そもそもメンバ変数phoneが持つメソッドをWorkerクラスが「オーバーライドする」と言う概念が存在しません。「違う」のではなく「存在しない」のです。

ところで、WorkerクラスのShowData()でphone.ShowData()を呼ぶことで「移譲」できそうに見えますね。でも、SalesやWorkerという種類の人間に対するShowData()をPhone(電話機)へ移譲して期待通りに動作するとは思えません。

つまり現在の例はオーバーライドでも移譲でもないです。

皆様は、具体的にどう使い分けているのでしょうか。

原則として、下記になると思います。
含まれる側の機能をほぼ全て、含む側のクラスでも提供したい時は継承
複数のインスタンスがあることが自然な時はメンバ変数

でも、あまり意識したことないです。本質的に異なるので悩むことはほとんどないのです。
継承はよく言われるように原則としてis_a関係です。PhoneをWorkerが継承するってことは"Worker is a Phone."(労働者は電話機の一種)ということになります。悩むまでもなく継承はありえないですね。


【追記】(長文失礼)
「コンポジション」について何か変な印象を受ける解説が多々あるので、追いかけてみました。

どうも「オブジェクト指向プログラミングへの道 5日目:オブジェクトコンポジション」や「【Effective Java】項目16:継承よりコンポジションを選ぶ」に問題がありそうです。(後者はEffective Javaの項目16が開示されていなかったので、Effective Javaへの突っ込みのために引用させて頂きました。)

オブジェクト指向プログラミングへの道 5日目:オブジェクトコンポジション

一般にAはBの特別な種類であるときは継承関係、AはBの性質であるときにはオブジェクトコンポジションを使うとよいとされている。 StudentというのはPersonの一種だろうか、それともPersonの性質にすぎないだろうか

  AはBの特別な種類であるとき:A is a B.
AはBの性質であるとき:B has a A.
ですから、
StudentというのはPersonの一種だろうか→Student is a Person.
StudentというのはPersonの性質にすぎないだろうか→Person has a Student.
ですね。

直後に下記のように書かれてます。

学生でかつ社会人なんて言う人がいるケースを扱わねばならないとしたら、性質と捉えてオブジェクトコンポジションを使うべきだろうね。

つまり、Personが学生という性質、および、社会人という性質の両方を持つ可能性に言及し、コンポジションが良いと言ってます。
従って、実装する場合は、下記ですね。

C++

1class Person 2{ 3 Student student; 4 Worker worker; 5};

しかし、例示されたプログラムでは下記のようになってます。

Java

1public class Student 2{ 3 private Person person; 4}

このプログラムは「人という性質を持っている学生」とモデル化してます。逆です。「学生という性質を持っている人」ですよね。
つまり、コンポジションの主旨に沿わない実装を提示して、継承より適切と結論付けてます。(あっはっは)

【Effective Java】項目16:継承よりコンポジションを選ぶ

継承はカプセル化を破壊する。 そのため、サブクラスはスーパークラスの実装に依存することになり、スーパークラスの実装が変わった場合、意図せずサブクラスの挙動が変わる可能性がある。

依存先のクラスをA、それを使っているクラスをB、更にBを使っているクラスをCとします。
「継承の場合はAの仕様を直接Bが公開するのでAが変化した時Bを使っているCにも影響する」、しかし、「コンポジションなら、Aを仕様変更しても影響はBにとどまりCに影響しないことが可能」と言いたいのだと思います。
でも逆に、Cにも反映しなければ行けないような仕様変更をAに施す際にBを触らなくても良い場合があり、それが継承のメリットです。コポジションの時はBも触らないと行けません。

要は継承とコンポジョンで仕様変更時の影響範囲が異なります。将来の拡張可能正を考慮しつつ、適切な方を選ぶべきです。コンポジョンを選んだ方が影響範囲が狭いと決めつけていますが根拠レスですね。

また、下記としてプログラムが掲載されてます。

サブクラスの危険性を示すため、HashSet を拡張した InstrumentedHashSet クラスを例にとる。

しかし、これは単なるバグです。InstrumentedHashSet<E>::addAll()はaddCount += c.size();してはいけないのに、仮想関数の意味を理解しないままやってしまったバグです。

要するに、バグりやすいので継承は使わない方が良いと言っていることになります。
確かに継承は単にメンバ変数として実装するより技術的に難易度高いので、素人は使うなって間違ってはいないですが、いくらなんでもそれは読者をバカにしすぎてないでしょうか?

投稿2016/01/25 13:58

編集2016/01/26 02:50
Chironian

総合スコア23272

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

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

Toyoshima

2016/01/27 09:29

Chironianさん 初めまして。 面白いですね! 「学生でかつ社会人」の状態が存在するのなら、まさにご指摘のとおりです! 後者も、「ただのバグ」と一蹴されていて痛快です。 「継承よりコンポジションを使うべき」と、短絡的に合理性を求めて結論づけるのはかえって視野を狭くしていると思います。 継承の目的は「機能追加」か「抽象機能の実装」が主ですが、コンポジションに関する議論は、前者の「機能追加」のみに焦点をあてて論じています。 さらに、既存処理の引数として該当クラスが求められる場合は、コンポジションを採用するわけにはいきません。 上記を全て無視したうえで、議論をすすめておりますね。。 非常に面白いサイトでした!
guest

0

http://cpplover.blogspot.jp/2010/08/overloadoverridehiding.html

まあ簡単に言うと、virtual関数の場合、基底クラス型変数で派生クラスを受けた場合、派生クラスの関数がよばれ、(派生クラスの関数が基底クラスの関数をoverrideしている場合)

一方単に遮蔽した時は基底クラスの関数がよばれるということかと。

virtual関数ってようは関数ポインタを持った構造体と同じですし

投稿2016/01/27 04:08

yumetodo

総合スコア5850

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

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

0

ご質問いただいた的をちょっと外してしまい申し訳ないのですが・・・

"オーバーライド"について、そのやり方と、使い道がまだ十分理解できてないところで混乱が生じている
と思います。

ご質問いただいてる疑問点を十分解消するには、ポリモフィズム(多態性)の使い道を理解することが必要ですね。

私の場合は、リファクタリングや、Java言語で学ぶデザインパターン入門という本を読んで心底理解できましたが・・・Javaなんです。。

身近な方に尋ねると、C++の良書を紹介いただけるかもしれませんが、上記の2つの本はおすすめします。

多態性など理解しなくても、プログラミングに差し支えない現場のほうが経験上多いものの・・・
やはり理解しておくと、できる仕事の幅が広がります。
時間をかけてでも、ご理解されようと努めることをおすすめします。

投稿2016/01/26 02:03

Toyoshima

総合スコア422

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

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

0

Phoneクラスを仮想関数(Virtual ShowData())にして、Workerクラスでその関数を実装する場合( ShowData())、通常のオーバーライドとどう違うのでしょうか。

仮想関数を持つクラスはインスタンスを持てません。つまりPhoneクラスは別のクラスの基底クラスとなって初めて使用できるクラスになります。
(訂正)「純粋仮想関数」の間違いです。単にvirtual修飾だとインスタンス作成できますね。訂正します。オーバーライドする関数にvirtualをつけるかつけないか、という点では「つけた方が意図しない不具合に悩まされない」と思います。ただ、virtualをつけるとその分呼び出しコストがかかるのは間違いないです。

PhoneクラスでShowData()が実装されていれば、Phoneクラスはそれだけでインスタンスを作ることができるので場面によっては単独で使用することができます。
(訂正)こちらも「純粋仮想関数」の場合です。すみません。

これらはクラス設計時にどういう意図で設計されているかによって違ってきます。

例えば、似た機能を持つクラスを複数作る必要があることが判っている場合、共通する機能を一つの基底クラスにまとめてそれぞれ継承させることになります。この時は共通して持たせたいメンバ関数を仮想関数として、派生クラスで必ず実装させるようにする、というようなことができます。

一方すでに使用しているクラスがあって、そのクラスの機能をちょっと拡張したい場面で、元のクラスを派生させて新しいクラスを作ったりすることがあります。この場合、元のクラスは元のクラスで使用している場面があり、拡張した機能のクラスはまた必要となる場面で使用する、ということになりますので、どちらもインスタンス化させる必要があります。この場合一部のメンバ関数をオーバーライドして機能拡張する、といったことをします。

なので

皆様は、具体的にどう使い分けているのでしょうか。

上記のようにクラス設計の段階で基底クラスをどう扱うか決めてあれば特に悩むこともないと思います。もし、悩むような状況になった場合、一度クラス設計を根底から見直してみることもいいかもしれません。

投稿2016/01/25 12:12

編集2016/01/25 13:48
KoichiSugiyama

総合スコア3041

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

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

Chironian

2016/01/25 12:53

横から失礼します。 > 仮想関数を持つクラスはインスタンスを持てません。 「純粋」仮想関数を持つクラスの間違いですね?
KoichiSugiyama

2016/01/25 13:49 編集

ご指摘のとおりです。仮想関数だとインスタンス化できますね。訂正します。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問