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

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

ただいまの
回答率

90.50%

  • C++

    3457questions

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

C++で複数のオブジェクト間で1つのオブジェクトを共有する方法

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 7
  • VIEW 4,446

yama_da

score 65

こんにちは。C++のことで質問です。
例えば、

class C
{
public:
    Data _data; 
};

class B
{
public:
    C _objectC;    
    Data _data;
};

class A
{
public:
    B _objectB;
    Data _data;    
};

class Data
{};


このように、AがBをメンバに持っていて、BがCをメンバに持っている状況で、DataというクラスのオブジェクトをA、B、Cそれぞれに共通して持たせたいのですが、(色々省略して書きます)

class C
{
public:
    Data _data;

    void setData(Data& d)
    {
        _data = d;
    }
};

class B
{
public:
    C _objectC;
    Data _data;

    void setData(Data& d)
    {
        _data = d;
        _objectC.setData(d);
    }
};

class A
{
public:
    B _objectB;    
    Data _data;

    void init()
    {
     _data.addData(/*データを色々セット*/);
        _objectB.setData(_data);
    }
};

class Data
{};


のように、参照渡しで_dataを渡していくという方法はC++的にアリなのでしょうか?参照渡しを使ったのは、Dataクラスはなんらかのデータを保持したりその中かから検索したりするだけのクラスなので、中身の全く同じオブジェクトのコピーをわざわざ3つも作るより、1つのオブジェクトを共有したほうがメモリの節約になるんじゃないかと思ったからです。それとも、

class C
{
    Data* _data;
    void setData(Data* d)
    {
        _data = d;
    }
};

class B
{
public:
    C _objectC;
    Data* _data;

    void setData(Data* d)
    {
        _data = d;
        _objectC.setData(d);
    }
};

class A
{
public:
    B _objectB;    
    Data _data;

    void makeData()
    {
        _data.addData(/*データを色々セット*/);
        _objectB.setData(&_data);
    }
};

class Data
{};


のように、Aでオリジナル?のDataオブジェクトを作り、そのポインタをB,Cに渡していくという方法のほうが良いでしょうか?というより、ポインタを使うこと以外でこの2つのやり方に違いはあるのでしょうか?同じ値を参照しているという点ではどちらも同じに思えるのですが、、、イマイチ参照渡しが理解できていないのかもしれません。よろしくお願いします。

<2017/1/26 追記>
こんばんは。皆さん回答ありがとうございます。僕の例えが少し大雑把すぎたので、もう少し付け加えさしてください。
AはB、Cを管理する1つのおおきなクラスで、プログラムの最初から最後まで生きている?(すみません、言葉が見つかりません。。)寿命の長いオブジェクトです。AはメンバにBをいくつか持っていて、そしてBもCを複数持っている、そしてCはこの中で言うと最小単位のようなオブジェクトです。また、B,CはAが削除されるまで削除されないオブジェクトです。このABCのオブジェクト全体で共有したいデータがいくつかあり、それをどうやって共有しようかと思い質問しました。

例えば、クラスCの中にData型のオブジェクトdataを用意して、clsAobj.clsBobj.clsCobj.dataのようにすればアクセスできますよね(public指定を前提とする)?

majiponiさんのおっしゃった、この方法ももちろん考えたのですが、Cは複数あるので、内容の同じオブジェクトをいくつも持つのは無駄ではないかと思い、使いませんでした。
質問しておいてなんだという話になりますが、少しでもメモリが節約できればと思い参照を使おうとしたけれど、実際ポインタを使った方法と参照を使った方法でメモリの使用量に大きなさはあるのでしょうか?

オブジェクトの共有目的で参照を使った場合、寿命の管理が難しくなり、また、混乱しやすいと思っています。

raccyさんのおっしゃったように、自分があとで見て混乱しないようにするためにも、ひねくれて参照なんて使わずに、ポインタを使ったほうがまだ安全なのではないかと、回答を読んでいるうちに思いました。また、シングルトンというのもあるようですが、そこまでするほどの物でもないしなぁといった感じです。結局、どうするのがベストなのでしょうか?

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 4

+5

まず、書いているコードについてですが、関数の引数には参照渡しですが、メンバーは参照では無いため、結局コピーになります。つまり、共有はされていません

#include <iostream>

class Data
{
public:
    int i = 0;
    void addData(int x)
    {
        this->i += x;
    }
};

class C
{
public:
    Data _data;
    void setData(Data &d)
    {
        _data = d;
    }
};

class B
{
public:
    C _objectC;
    Data _data;
    void setData(Data &d)
    {
        _data = d;
        _objectC.setData(d);
    }
};

class A
{
public:
    B _objectB;
    Data _data;
    void init()
    {
        _data.addData(3);
        _objectB.setData(_data);
    }
};

int main()
{
    A a;
    a.init();
    a._data.addData(2);
    std::cout << "a._data.i = " << a._data.i << std::endl;
    std::cout << "a._objectB._data.i = " << a._objectB._data.i << std::endl;
    std::cout << "a._objectB._objectC._data.i = "
              << a._objectB._objectC._data.i << std::endl;
    return 0;
}

上を実行して見ればわかるように、_dataメンバーが違う物になっています。Chironianさんの指摘通り、メンバーも参照にしないと共有してメモリ消費量を減らすという目的は達成できていません。(関数の引数を参照渡しにすること自体が無意味というわけではありません)

さて、メンバーも参照にした場合、気をつけなければいけないのは大本のオブジェクトの寿命です。大本のオブジェクトが削除されると、参照先がどうなっているか不定になります。

#include <iostream>

class A
{
public:
    int &i;
    A(int &i) : i(i)
    {
    }
};

int main()
{
    A *a;
    {
        int x = 42;
        a = new A(x);
    }
    std::cout << a->i << std::endl;
    delete a;
    return 0;
}

aが破棄される前にxは破棄されます。ブロックを抜けるとxが破棄されているため、xを参照しているa->iの値は不定です。環境によっては42のままの場合もありますが、paiza.ioで試す0になり、期待通りになりません。

ただし、これは参照では無くただのポインタにした場合も同じ事になります。破棄を防ぎながら、かつ、メモリ消費量を減らすとなるとスマートポインタを使うしかありません。
※ 下記コードはC++14以上で無いと動きません。

#include <iostream>
#include <memory>

class A
{
public:
    std::shared_ptr<int> i;
    A(std::shared_ptr<int> i) : i(i)
    {
    }
};

int main()
{
    std::unique_ptr<A> a;
    {
        std::shared_ptr<int> x = std::make_shared<int>(42);
        a = std::make_unique<A>(x);
    }
    std::cout << *(a->i.get()) << std::endl;
    return 0;
}

xはshared_ptrであるため、xが破棄されても、aの中のi(同じ先を見ているshare_ptr)はそのままあるので、その先はそのまま維持されます。そして、aが破棄されるときにi(の中身)も破棄されます。ただし、shared_ptrは参照カウント方式であるため、

  • ただのポインタの処理よりも僅かに遅い
  • 循環参照は自動的に破棄できない

という欠点がありますので、注意が必要です。


私個人の意見ですが、オブジェクトの共有目的で参照を使った場合、寿命の管理が難しくなり、また、混乱しやすいと思っています。むしろ、オブジェクトの寿命について意識しなければならないポインタを使った方がまだいいと思っています。私が書く時は、値渡しだとパフォーマンスが低下することを防ぐ目的以外では基本的に参照を使いません。


【追記】

アイデアの一つですが、親子関係を強調するなら、親のポインタを持たせるという手もあります。(コードは割と適当なので参考程度に)

#include <iostream>
#include <memory>

class Data;
class A;
class B;
class C;

class Data
{
private:
    int val;

public:
    Data(int val = 0) : val(val){};
    void addData(int x)
    {
        this->val += x;
    }
    int getVal()
    {
        return this->val;
    }
};

class A
{
    friend B;
    friend C;

private:
    Data _data;
    std::unique_ptr<B> _objectB;

public:
    A(int val = 0) : _data(val), _objectB(std::make_unique<B>(this))
    {
    }
    Data &getData()
    {
        return this->_data;
    }
    B *getB()
    {
        return this->_objectB.get();
    }
    void addData(int x)
    {
        this->_data.addData(x);
    }
};

class B
{
    friend C;

private:
    A *const parent;
    std::unique_ptr<C> _objectC;

public:
    B(A *parent) : parent(parent), _objectC(std::make_unique<C>(this))
    {
    }
    C *getC()
    {
        return this->_objectC.get();
    }
    Data &getData()
    {
        return this->parent->_data;
    }
};

class C
{
private:
    B *const parent;

public:
    C(B *parent) : parent(parent)
    {
    }
    Data &getData()
    {
        return this->parent->parent->_data;
    }
};

int main()
{
    auto a = std::make_unique<A>(0);
    a->addData(2);
    std::cout << "a _data: " << a->getData().getVal() << std::endl;
    std::cout << "a _objectB _data: " << a->getB()->getData().getVal()
              << std::endl;
    std::cout << "a _objectB _objectC _data: "
              << a->getB()->getC()->getData().getVal() << std::endl;
    return 0;
}

親で共有させたいメンバーが増えていってもそれぞれの子でメンバーも宣言をする必要も無いですし、子が持つメンバーも親一人のポインタだけで済みます。ただ、親のコンストラクタでしか子が作れないので、ポインタにしないと駄目だったはず…。オブジェクト間で親子関係を持たせられるQtがそんな作りだったと思いました(Qtは自前で参照カウントとかしていたはずなので、もうちょっと複雑ですが)。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/01/26 12:42

    > オブジェクトの共有目的で参照を使った場合、寿命の管理が難しくなり

    なるほど、はまりポイントの一つですね。

    しかし、オブジェクトの依存関係をきちんと設計できていればありえない事態ではありますし、本質的な対策は依存関係を適切に設計することと思います。ここを手を抜くと後でひどい目にあいます。
    その設計ミスの検出には有効と思います。

    ただ、安易にshard_ptrを使うと依存関係の設計に手を抜いてしまい、負のスパイラルに陥ることはないでしょうか?
    個人的には、shared_ptrは複数の管理主体を許す本質的に頭の痛い構造になるので、慎重に設計した結果shared_ptrがベストと判断出来た時に限定して使うものと考えています。

    キャンセル

  • 2017/01/26 20:29

    >上を実行して見ればわかるように、_dataメンバーが違う物になっています。Chironianさんの指摘通り、メンバーも参照にしないと共有してメモリ消費量を減らすという目的は達成できていません。
    変数も参照を使わなければいけないんですね、知りませんでした。
    追記したので、よろしければまた回答よろしくお願いします。

    キャンセル

  • 2017/01/27 02:34

    > ただ、安易にshard_ptrを使うと依存関係の設計に手を抜いてしまい、負のスパイラルに陥ることはないでしょうか?

    そうですね。shared_ptrはGCのように完全ではありませんから、頼り切ると循環参照でメモリリークしまくっていたに陥りますからね。タイトルのテーマとして「複数のオブジェクトから」とあったので、親子関係とかがない場合を想定してあげてみました。

    キャンセル

  • 2017/01/29 05:06

    回答ありがとうございます。あれから少しして、データだけじゃなく関数やらなんやらも含めたくなったので、結局シングルトンでいってみることにしました。お世話になりました、また機会があればよろしくお願いします。

    キャンセル

+3

こんにちは。

どちらも有りです。

なお、参照で渡せる時は参照で渡しておいた方がバグが発生しにくいです。
ポインタはnullptrにできますし、ポイント先を変更できます。
これらの操作が不要な時は参照を使うことでバグを避けやすくなります。


【追記】
ああ、でもよく見ると参照渡ししてますが、受け取り側はコピーで受け取ってますね。
それは勿体無いです。

class C
{
public:
    Data& _data;

    C(Data& iData) : _data(iData)
    { }
};

class B
{
public:
    Data& _data;
    C _objectC;

    B(Data& iData) : _data(iData), _objectC(_data)
    { }
};

class A
{
public:
    Data _data;
    B _objectB;    

    A() : _objectB(_data)
    {
        _data.addData(/*データを色々セット*/);
    }
};

参照はコンストラクト時にのみ設定できますので、初期化子を使って初期化する必要があります。

そして、上記の初期化の場合、B, Cのオブジェクトがコンストラクトされる時はまだ_dataはデフォルト・コンストラクタでのみ初期化されおり、addData()は呼ばれていません。
addData()関数ではなく、Dataクラスのデフォルト・コンストラクタでaddData()関数と同じ初期化をできると好ましいです。
もし、パラメータを渡す必要がある場合は、それなりに工夫する必要があります。


【追加の質問への回答】
依存関係をきっちり設計することは何れにせよ必須ですね。そして、きちんと設計されてますのできっちりプログラムすることが可能です。ですので、私なら参照を選択します。

ただ、もう少し知識が必要です。例えば、_objectBのデストラクタで_dataをアクセスするのであればその時まで_dataが生きている必要が有ります。_dataを_ojectBより先に定義するだけでそれを実現できますが、そのことを知っている人は少ないと思います。

C++11の文法と機能12.4 デストラクター(Destructors)より。

デストラクターの呼び出しは、コンストラクター呼び出しの逆順に行われる。コンストラクター呼び出しの順番については、12.6.2 基本クラスとデータメンバーの初期化を参照。
非staticデータメンバーの初期化の順番は、クラス定義の中でメンバーが宣言されている順番である。

そこで、深い知識を必要としないようなプログラミングをするのも1つの方針と思います。
shared_ptrを使えば、デストラクト時の細かいタイミングを考慮する必要を回避できます。
なお、Dataクラス内でAクラスをアクセスしたくなった時に、同じようにshard_ptrを使ってクラスAのインスタンスへのポインタをDataクラスで保持すると循環参照します。shard_ptrに頼って依存関係の設計に手を抜かなければ、大丈夫と思いますが。


【更に追記】
あっとと、シングルトンを使える状況(クラスAのインスタンスは1つしかない)であれば、単純に_dataをstaticメンバにすれば簡単です。別にシングルトン化する必要は特にありません。
みんな、A::_dataでアクセスできるようになります。
スケスケすぎて怖い場合は、_dataをprivateにしてBとCをAのfriendにすればOKです。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/01/26 20:32

    >参照はコンストラクト時にのみ設定できますので、初期化子を使って初期化する必要があります。
    ご指摘ありがとうございます、知りませんでした。
    追記したので、よろしければまた回答よろしくお願いします。

    キャンセル

  • 2017/01/29 05:09

    回答ありがとうございます。あれから少しして、データだけじゃなく関数やらなんやらも含めたくなったので、結局シングルトンでいってみることにしました(自分でやらないと言っていおきながらなんですが、、、)。お世話になりました、また機会があればよろしくお願いします。

    キャンセル

+2

オブジェクト間に必ず所有関係があるなら、共有なんてしないのが一番だと思います。

…割と真面目な話、わざわざ共有させるメリットなんてあんまりないわけで、同じオブジェクトにアクセスする方法があればそれでよいのです。例えば、クラスCの中にData型のオブジェクトdataを用意して、clsAobj.clsBobj.clsCobj.dataのようにすればアクセスできますよね(public指定を前提とする)? この方法ならオーバーヘッドも0です。

…と書くと、先にコメントされたお二人から脳筋だと起こられそうなので補足。この方法の問題点は、やはりアクセス制御でしょう。不用意に内部オブジェクトを公開することになるので、不本意な使われ方に対して脆弱になります。アクセッサ、フレンド設定などをしても同様の問題は起こります。

提案のあった方法の中から選ぶのであれば、私は生のポインタを選びます。副作用の存在に一番シビアになれるのは、ポインタだと思うのです。(スマポは堅牢なのですが、個人的には余計な部分に意識が行ってしまうので…。)

ですが、ポインタで共有すべきだと私は言っていません。オブジェクトの値渡しや返り値で実現できるのにそれをしない、あるいはそもそも実現できないのは、設計に問題があることがほとんどです。多少便利、オーバーヘッドも少ないかもしれませんが、その共有、本当に必要ですか?

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/01/26 20:28

    回答有り難うございます。追記しました、よろしければまた回答よろしくお願いします。

    キャンセル

  • 2017/01/26 20:31 編集

    グローバル変数(禁じ手)はどうですか?
    まあ、悪魔の囁きはさておき、変数をコンストラクトするときに参照なりポインタで要求するのって、グローバル変数と大して変わりないので、個人的にはあまりオススメしません。

    使う必要があるときに一々メソッドの引数で参照を要求してもいいんですよ? そちらのほうが副作用とかはるかに分かりやすいですし。

    キャンセル

  • 2017/01/29 05:01

    回答ありがとうございます。あれから少しして、データだけじゃなく関数やらなんやらも含めたくなったので、結局シングルトンでいってみることにしました。お世話になりました、また機会があればよろしくお願いします。

    キャンセル

checkベストアンサー

0

ポインタで共有して、寿命管理は、ref counterで行います。
親a, b, c で共有される Eを共有、a がEを使用し始めるときに、Eのref counter++, ~aするときにEのref counter--, ref counter == 0 なら親側からdelete.が基本戦略。

aのEをbでも使うときは、a->shareをbで使う。

class cse {
private:
int cnt ;
public:
cse () {cnt=1;}
cse * attach() {cnt++;return this;}
bool remove() {return --cnt;}
} ;
class csa {
private:
cse *ref ;
public:
csa () {ref = new cse() ;}
csa (cse * pe) {ref = pe->attach() ;}
cse * share() {return ref;}
~csa() {
if( ref->remove()) {delete ref;}
}
} ;
aでaのEを使うときは、循環する。防止策は、aがEを既に持っているときに、Eを付け変える動作を禁止する。
aのコンストラクタだけでEへの参照を変更できるようにすれば、自然に禁止できる。delete this できればコードはきれいになる。でもdelete this って安全かしら?

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

  • ただいまの回答率 90.50%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    ポリモーフィズムの仕組み

    ポリモーフィズムの実装で、原型クラスのポインタを使って、なぜ派生クラスを指し示すようになったのでしょうか? class Girl;public Humanというのは物理的にどのよ

  • 受付中

    C++クラスの、Privateなメンバ変数を隠蔽したい

    前提・実現したいこと C++のクラス宣言をヘッダーとCPPファイルに分けて行う場合、Privateメンバ変数もヘッダーに記述しなければなりません。Privateにも関わらず外部か

  • 受付中

    C++ オブジェクト指向 staticの使用について

    C++前提で話します。 static修飾子がありますが、classを使っていてstaticが必要だと思ったことがありません。 staticでできることは理解していますがやはり使

  • 解決済

    授業で扱われたプログラムが理解できません

    授業で他の生徒が書いたプログラムです。私にはまだまだ理解できません。これを紐解いていきたいのですが、まず、3行目の std::string name; ってどういう意味です

  • 解決済

    ポインタのポインタがよくイメージできない

     前提・実現したいこと ※初投稿です。不手際がありましたら申し訳ございません。 ①2つの文字列(char *a,char *b)に対して、辞書順を判定する関数int hantei(

  • 解決済

    C++のポインタについて

    ポインタについての質問があります。 1つ目に、型が違うポインタは、どう解釈されるんでしょうか?? char qtr = 3289; int * ptr = &qtr; こ

  • 解決済

    C++ メインクラスと内部クラスの関係について

     前提・実現したいこと 度々 お世話になっております。よろしくお願いいたします。 現在、バイナリファイルを読み込み、解析してテキストファイルへ出力するプログラムを作成中です。

  • 解決済

    カプセル化のやり方 get set

    下記のプロググラムで すべてのフィールドをカプセル化したいのですが どのようにコーディングしていいか分かりません。 private を型の前に付けた後のget setのつけ方がわか

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

  • C++

    3457questions

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