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

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

ただいまの
回答率

90.48%

  • C++

    4504questions

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

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

受付中

回答 4

投稿

  • 評価
  • クリップ 1
  • VIEW 1,979

TAKAYUKI_MIWA

score 64

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+1

こんにちは。

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

class Person
{
    Student student;
    Worker worker;
};

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

public class Student
{
    private Person person;
}


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

 【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/27 18:29

    Chironianさん
    初めまして。

    面白いですね!

    「学生でかつ社会人」の状態が存在するのなら、まさにご指摘のとおりです!
    後者も、「ただのバグ」と一蹴されていて痛快です。

    「継承よりコンポジションを使うべき」と、短絡的に合理性を求めて結論づけるのはかえって視野を狭くしていると思います。

    継承の目的は「機能追加」か「抽象機能の実装」が主ですが、コンポジションに関する議論は、前者の「機能追加」のみに焦点をあてて論じています。

    さらに、既存処理の引数として該当クラスが求められる場合は、コンポジションを採用するわけにはいきません。

    上記を全て無視したうえで、議論をすすめておりますね。。

    非常に面白いサイトでした!

    キャンセル

0

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

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

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

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

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

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

なので

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/01/25 21:53

    横から失礼します。

    > 仮想関数を持つクラスはインスタンスを持てません。

    「純粋」仮想関数を持つクラスの間違いですね?

    キャンセル

  • 2016/01/25 22:30 編集

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

    キャンセル

0

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

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

同じタグがついた質問を見る

  • C++

    4504questions

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