4

注:これは、私がしばらく前に投稿した質問の完全な言い回しです。重複している場合は、もう一方を閉じてください。

私の問題は非常に一般的ですが、具体的な簡単な例に基づいて、より簡単に説明できるようです。オフィスの電力消費量をシミュレートしたいとします。照明と暖房しかないと仮定しましょう。

class Simulation {
    public:
        Simulation(Time const& t, double lightMaxPower, double heatingMaxPower)
            : time(t)
            , light(&time,lightMaxPower) 
            , heating(&time,heatingMaxPower) {}

    private:
        Time time; // Note : stack-allocated
        Light light;
        Heating heating;
};

class Light {
    public:
        Light(Time const* time, double lightMaxPower)
            : timePtr(time)
            , lightMaxPower(lightMaxPower) {}

        bool isOn() const {
            if (timePtr->isNight()) {
                return true;
            } else {
                return false;
            }
        }
        double power() const {
            if (isOn()) {
                return lightMaxPower;
            } else {
                return 0.;
            }
    private:
        Time const* timePtr; // Note : non-owning pointer
        double lightMaxPower;
};

// Same kind of stuff for Heating

重要な点は次のとおりです。

1.Timeデータ メンバーに移動できない、LightまたはHeatingその変更がこれらのクラスのいずれにも由来しないため。

2.Timeにパラメーターとして明示的に渡す必要はありませんLight。実際、Lightプログラムのどの部分にも、パラメーターとして提供したくない への参照が存在する可能性がTimeあります。

class SimulationBuilder {
    public:
        Simulation build() {
            Time time("2015/01/01-12:34:56");
            double lightMaxPower = 42.;
            double heatingMaxPower = 43.;
            return Simulation(time,lightMaxPower,heatingMaxPower);
        }
};

int main() {
    SimulationBuilder builder;
    auto simulation = builder.build();

    WeaklyRelatedPartOfTheProgram lightConsumptionReport;

    lightConsumptionReport.editReport((simulation.getLight())); // No need to supply Time information 

    return 0;
}

これで、Simulationコピー/移動が構築されていない限り、完全に見つかります。そうである場合は、コピー/移動も構築され、デフォルトで、Time へのポインターはコピー/移動元の古いインスタンスをLight指しているからです。ただし、実際に、return ステートメントとオブジェクト作成の間で copy/move が構築されます。TimeSimulationSimulationSimulationBuilder::build()main()

現在、問題を解決する方法はいくつかあります。

1: コピー省略に依存します。この場合(そして私の実際のコードでは)、コピーの省略は標準で許可されているようです。しかし必須ではなく、実際のところ、clang -O3 によって省略されることはありません。より正確に言うと、clang はSimulationコピーを省略しますが、move ctor for を呼び出しますLight。また、実装依存の時間に依存することは堅牢ではないことに注意してください。

2: で move-ctor を定義しSimulationます。

Simulation::Simulation(Simulation&& old) 
    : time(old.time)
    , light(old.light)
    , heating(old.heating)
{
    light.resetTimePtr(&time);
    heating.resetTimePtr(&time);
}

Light::resetTimePtr(Time const* t) {
    timePtr = t;
}

これは機能しますが、ここでの大きな問題はカプセル化を弱めることです:移動中により多くの情報が必要でSimulationあることを知る必要があります。Lightこの単純化された例では、これはそれほど悪くはありませんが、timePtr直接ではなくLight、そのサブサブサブメンバーの 1 つにあると想像してください。それから私は書かなければならないでしょう

Simulation::Simulation(Simulation&& old) 
    : time(old.time)
    , subStruct(old.subStruct)
{
    subStruct.getSubMember().getSubMember().getSubMember().resetTimePtr(&time);
}

これは、カプセル化とデメテルの法則を完全に破ります。関数を委任するときでさえ、私はそれが恐ろしいと思います。

Time3: によって監視されているある種のオブザーバー パターンを使用し、メッセージを受信するときにポインターを変更するLightように、コピー/移動が構築されているときにメッセージを送信します。Light私はそれの完全な例を書くのが面倒だと告白しなければなりません。

4: で所有ポインタを使用しますSimulation

class Simulation {
    private:
        std::unique_ptr<Time> const time; // Note : heap-allocated
};

が移動Simulationすると、Timeメモリは ではないため、 のポインタLightは無効になりません。実際、これは他のほとんどすべてのオブジェクト指向言語が行うことです。すべてのオブジェクトはヒープ上に作成されるからです。今のところ、私はこの解決策を支持していますが、まだ完全ではないと考えています。B. Stroustrup が、必要のないときはポインタを使用すべきではなく、必要な場合は多かれ少なかれポリモーフィックであることを意味すると言っているのを聞いたことがあります。

5:Simulation返されることなく、その場で構築しSimulationBuilderます (その後、コピー/移動 ctor/代入をSimulationすべて削除できます)。例えば

class Simulation {
    public:
        Simulation(SimulationBuilder const& builder) {
            builder.build(*this);
        }

    private:
        Time time; // Note : stack-allocated
        Light light;
        Heating heating;
        ...
};



class SimulationBuilder {
    public:
        void build(Simulation& simulation) {

            simulation.time("2015/01/01-12:34:56");
            simulation.lightMaxPower = 42.;
            simulation.heatingMaxPower = 43.;
    }
};

今、私の質問は次のとおりです。

1: どのようなソリューションを使用しますか? もう一つ考えてみませんか?

2: 元のデザインに何か問題があると思いますか? それを修正するためにあなたは何をしますか?

3: このようなパターンに遭遇したことがありますか? 私のコード全体でかなり一般的です。Timeただし、実際にはポリモーフィックであり、ヒープが割り当てられているため、一般的にはこれは問題ではありません。

4: 問題の根本に戻ると、「移動する必要はありません。移動できないオブジェクトをその場で作成したいだけですが、コンパイラはそうすることができません」なぜないのですか? C ++での簡単な解決策と、別の言語での解決策はありますか?

4

4 に答える 4

2

すべてのクラスが同じ const (したがって不変) 機能にアクセスする必要がある場合、コードをクリーンで保守可能にするための (少なくとも) 2 つのオプションがあります。

  1. SharedFeature参照ではなくのコピーを保存します -SharedFeatureが小さくステートレスである場合、これは合理的です。

  2. std::shared_ptr<const SharedFeature>const への参照ではなく a を保存します。これはすべての場合に機能し、追加の費用はほとんどかかりません。std::shared_ptrもちろん、完全に移動を認識します。

于 2015-04-02T22:17:00.980 に答える
1

編集:クラスの命名と順序付けのために、2 つのクラスが無関係であるという事実を完全に見逃していました。

「機能」という抽象的な概念を説明するのは本当に難しいですが、ここで考えを完全に変えます。機能の所有権を に移動することをお勧めしMySubStructます。これで、コピーと移動が正常に機能MySubStructするようになりました。これは、それを知っているだけで、正しいコピーを作成できるためです。ここMyClassで、機能を操作できるようにする必要があります。したがって、必要に応じて委任をMySubStruct:に追加するだけですsubStruct.do_something_with_feature(params);

あなたの機能が sub struct AND の両方からのデータ メンバーを必要とする場合、責任を間違って分割し、 andMyClassの分割にまでさかのぼって再考する必要があると思います。MyClassMySubStruct

MySubStructの子であるという仮定に基づく元の回答MyClass

正しい答えはfeaturePtr、子から削除し、親で機能する適切な保護されたインターフェイスを提供することだと思います(注:ここでは、単なるget_feature()関数ではなく、抽象的なインターフェイスを意味します)。その場合、親は子について知る必要がなく、子は必要に応じて機能を操作できます。

完全に明確にするために、親クラスに というメンバーさえあることをMySubStruct 知りませんfeature。たとえば、おそらく次のようなものです。

于 2015-04-02T16:56:38.850 に答える