C++ 指向堆栈分配对象和移动构造的指针

C++ 指向堆栈分配对象和移动构造的指针,c++,pointers,move-semantics,copy-elision,rvo,C++,Pointers,Move Semantics,Copy Elision,Rvo,注:这是一个完整的重新措辞我张贴了一段时间。如果发现重复,请关闭另一个 我的问题很一般,但似乎可以根据一个具体的简单例子来解释。 想象一下,我想模拟办公室里的用电量。让我们假设只有一盏灯和暖气 class Simulation { public: Simulation(Time const& t, double lightMaxPower, double heatingMaxPower) : time(t) , li

注:这是一个完整的重新措辞我张贴了一段时间。如果发现重复,请关闭另一个

我的问题很一般,但似乎可以根据一个具体的简单例子来解释。 想象一下,我想模拟办公室里的用电量。让我们假设只有一盏灯和暖气

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
。事实上,在程序的任何部分中,如果不想将时间作为参数提供,则可能存在对灯光的引用

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;
}
现在,
模拟
是完美的,只要它不是复制/移动构造的。因为如果是,
Light
也将构造复制/移动,默认情况下,指向时间的指针将指向从中复制/移动的旧
模拟
实例中的
时间
。 但是,
Simulation
实际上是在
SimulationBuilder::build()
中的return语句和
main()中的对象创建之间构造的copy/move

现在有很多方法可以解决这个问题:

1:依赖复制省略。在这种情况下(在我的真实代码中),复制省略似乎是标准允许的。但不是必需的,事实上,它并没有被clang-O3所忽略。更准确地说,clang省略了
Simulation
copy,但确实调用了
Light
的move-ctor。还要注意,依赖依赖于实现的时间是不可靠的

2:在
模拟中定义移动向量

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;
}
class Simulation {
    private:
        std::unique_ptr<Time> const time; // Note : heap-allocated
};
这确实有效,但最大的问题是它削弱了封装:现在
模拟
必须知道
灯光
在移动过程中需要更多信息。在这个简化的示例中,这并不太糟糕,但是想象一下
timePtr
不是直接在
Light
中,而是在它的一个子成员中。那我就得写了

Simulation::Simulation(Simulation&& old) 
    : time(old.time)
    , subStruct(old.subStruct)
{
    subStruct.getSubMember().getSubMember().getSubMember().resetTimePtr(&time);
}
这完全打破了封装和德米特定律。即使在委派职能时,我也觉得很可怕

3:使用某种观察者模式,其中,
观察时间
,并在复制/移动时发送消息,以便,
在接收消息时更改其指针。 我必须承认,我懒得写一个完整的例子,但我认为它将是如此沉重,我不确定增加的复杂性是否值得

4:在模拟中使用拥有的指针:

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;
}
class Simulation {
    private:
        std::unique_ptr<Time> const time; // Note : heap-allocated
};
现在我的问题是:

1:您会使用什么解决方案?你想到另一个吗

2:你觉得原来的设计有问题吗?你会怎么做来修复它

3:你有没有遇到过这种模式?我发现它在我的代码中相当普遍。一般来说,这不是问题,因为时间确实是多态的,因此堆是分配的


4:回到问题的根源,那就是“没有必要移动,我只希望在适当的位置创建一个不可移动的对象,但编译器不允许我这样做”为什么C++中没有简单的解决方案,在另一种语言中有解决方案吗?< /P> < P>编辑:由于类的命名和排序,我完全忽略了你的两个类是无关的事实。 用“功能”这样一个抽象的概念来帮助你真的很难,但我要完全改变我的想法。我建议将该功能的所有权转移到
MySubStruct
。现在,复制和移动可以正常工作,因为只有
MySubStruct
知道它,并且能够进行正确的复制。现在,
MyClass
需要能够对功能进行操作。因此,在需要的地方,只需将委托添加到
MySubStruct
subStruct.do\u something\u与功能(params)

如果您的功能需要来自子结构和
MyClass
的数据成员,那么我认为您的职责划分不正确,需要重新考虑
MyClass
MySubStruct
的划分

原始答案基于以下假设,即
MySubStruct
MyClass
的孩子:

我认为正确的答案是从孩子身上删除
featurePtr
,并为父母提供一个适当的受保护接口(注意:我这里真正指的是一个抽象接口,而不仅仅是
get\u feature()
函数)。然后,父对象不必知道子对象,子对象可以根据需要对功能进行操作


要完全清楚:
MySubStruct
将不知道父类甚至有一个名为
feature
的成员。例如,可能是这样的:

如果所有类都需要访问相同的常量(因此是不可变的)特性,那么您(至少)有两个选项可以使代码保持干净和可维护:

class SimulationBuilder {
    public:
        template< typename SimOp >
        void withNewSimulation(const SimOp& simOp) {
            Time time("2015/01/01-12:34:56");
            double lightMaxPower = 42.;
            double heatingMaxPower = 43.;
            Simulation simulation(time,lightMaxPower,heatingMaxPower);
            simOp( simulation );
        }
};

int main() {
    SimulationBuilder builder;

    builder.withNewSimulation([] (Simulation& simulation) {

        WeaklyRelatedPartOfTheProgram lightConsumptionReport;

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

    return 0;
}
  • 存储
    SharedFeature
    的副本而不是引用-如果
    SharedFeature
    既小又无状态,这是合理的

  • 存储一个
    std::shared_ptr
    而不是对const的引用-这在所有情况下都有效,几乎没有额外费用<代码>std::shared_ptr
  • 当然是完全移动感知的

    1:您会使用什么解决方案?你想到另一个吗

    为什么不应用一些设计模式呢?我在您的解决方案中看到了工厂和单体的用途。可能还有一些其他的,我们可以声称的工作,但我在应用过程中的模拟比任何其他工厂更有经验

    • 模拟变成了单例
    build()
    函数
    class SimulationBuilder {
        public:
            template< typename SimOp >
            void withNewSimulation(const SimOp& simOp) {
                Time time("2015/01/01-12:34:56");
                double lightMaxPower = 42.;
                double heatingMaxPower = 43.;
                Simulation simulation(time,lightMaxPower,heatingMaxPower);
                simOp( simulation );
            }
    };
    
    int main() {
        SimulationBuilder builder;
    
        builder.withNewSimulation([] (Simulation& simulation) {
    
            WeaklyRelatedPartOfTheProgram lightConsumptionReport;
    
            lightConsumptionReport.editReport((simulation.getLight())); // No need to supply Time information
        } 
    
        return 0;
    }