是否应该将类中的所有对象作为引用?

时间:2016-03-09 23:11:56

标签: c++ include forward-declaration

总是让我的对象成员的数据类型成为引用是不好的做法吗?

阅读此question about avoiding #include后,我一直试图避免在我当前的C ++项目中使用#include,并始终转发声明我的数据类型。

以下是我项目中的示例代码段。在标题中我有EnemyBuilder& m_builder;然后我在源代码中初始化它。

EnemyFactory.h

#include <string>

#ifndef ENEMYFACTORY
#define ENEMYFACTORY

class World;
class Enemy;
class EnemyBuilder;

class EnemyFactory
{
private:
    EnemyBuilder &m_builder;
    World &m_world;

public:
    EnemyFactory(World &world);

    Enemy* produce(std::string type);
};

#endif

EnemyFactory.cpp

#include "EnemyFactory.h"

#include "EnemyBuilder.h"
#include "World.h"
#include "Enemy.h"

EnemyFactory::EnemyFactory(World &world) :
    m_world(world), m_builder(EnemyBuilder())
{

}

Enemy* EnemyFactory::produce(std::string type)
{
    Enemy *product = new Enemy(m_world);

    if (type == "basic")
    {
        product->setSpeed(5000.0f);
    }

    return product;
}

3 个答案:

答案 0 :(得分:1)

  

总是让我的对象成员的数据类型成为引用是不好的做法吗?

不仅是不好的做法,而且效率低,限制性强。

  

为什么?

  1. 包含引用的类的对象是自动不可分配的。该标准有std :: reference_wrapper&lt;&gt; (基本上是一个包装指针)在将引用类型绑定到函数对象时绕过此限制。

  2. 您有义务管理对象的生命周期。由于您要存储引用而不是对象,因此您的类不可能自行清理。此外,它无法确保它所引用的对象仍然存在。你刚刚失去了c ++给你的所有保护。欢迎来到石器时代。

  3. 效率较低。现在所有对象访问都通过本质上是一个指针来解除引用。而你最有可能失去缓存局部性。没有意义的。

  4. 您失去了将对象视为值的能力。这将导致效率低下,错综复杂的计划。

  5. 我可以继续......

  6.   

    我该怎么办?

    首选值语义(将对象视为值)。让他们被复制(如果你很好地编写你的课程,大多数副本都会被删除)。将它们封装并组合成更大的对象。让容器管理组件的生命周期。这是它的工作。

      

    包含一些头文件的时间成本怎么样?

    除非您在数百(数千)个模板类中进行可怕的复杂模板扩展,否则它甚至不太可能成为问题。制作咖啡的时间比编写一些源文件要多。

答案 1 :(得分:0)

首先查看Richard Hodges' answer ...在您当前的代码中,您正在为自己堆积火药桶,您可能有一天会点燃它...... ......总有一天... ... / em>要解决这个问题。

  

在EnemyFactory.h中,你有一行......喜欢使用   std::unique_ptr

 Enemy* produce(std::string type);
     

这样,这就变成了

std::unique_ptr<Enemy> produce(std::string type);

再次在EnemyFactory.cpp ....

  

你有可怕的Undefined Behaviour,基本上是你的程序   因为你绑定了对临时对象的引用而崩溃了

EnemyFactory::EnemyFactory(World &world) :
    m_world(world), m_builder(EnemyBuilder())  //<---This will crash
{

}

您尝试做的更大范围适用于PIMPL成语。

接近这个的最佳方法是

EnemyFactory.h

#include <string>
#include <memory>

#ifndef ENEMYFACTORY
#define ENEMYFACTORY

class World;
class Enemy;
class EnemyBuilder;

class EnemyFactory
{
private:
    std::unique_ptr<EnemyBuilder> m_builder;
    std::shared_ptr<World> m_world;  //<- I am guessing World is a shared object based on your constructor

public:
    EnemyFactory(World &world);

    std::unique_ptr<Enemy> produce(std::string type);
};

#endif

在EnempyFactory.cpp

#include "EnemyFactory.h"

#include "EnemyBuilder.h"
#include "World.h"
#include "Enemy.h"

//I hope it is guaranteed the instance if World, world will always outlive any instance of EnemyFactory...

EnemyFactory::EnemyFactory(World &world) :
    m_world(&world), m_builder(std::make_unique<EnemyBuilder>())
{

}

std::unique_ptr<Enemy> EnemyFactory::produce(std::string type)
{
    std::unique_ptr<Enemy> product = std::make_unique<Enemy>(m_world);

    if (type == "basic")
    {
        product->setSpeed(5000.0f);
    }

    return product;
}

我们这里没有使用引用,因为所有引用的类成员都必须在构造函数中实例化,管理生命周期可能是一团糟,取决于你的用例虽然....

指针在这里更方便....请记住,每当指针出现在你的脑海中时,首先选择std::unique_ptr,当共享对象时,你可以选择'std :: shared_ptr`。

答案 2 :(得分:-1)

我认为你是用锤子杀死蚂蚁。前向声明是一种工具,但并不总是一种最佳实践&#34;。您必须在某人(或您)中思考以后阅读您的代码,他们必须了解您的代码在做什么。

我建议你采用这种方法:

EnemyFactory.h

 #include <string>

#ifndef ENEMYFACTORY
#define ENEMYFACTORY

class EnemyFactory
{
private:
    EnemyBuilder m_builder;
    World m_world;

public:
   EnemyFactory(World &world);

   Enemy* produce(std::string type);
};

#endif

EnemyFactory.cpp

#include "EnemyBuilder.h"
#include "World.h"
#include "Enemy.h"

#include "EnemyFactory.h"// this include must be last one
EnemyFactory::EnemyFactory(World &world)
{

}

Enemy* EnemyFactory::produce(std::string type)
{
    Enemy *product = new Enemy(m_world);

    if (type == "basic")
    {
        product->setSpeed(5000.0f);
    }

    return product;
}

包含顺序稍有变化,您可以省略前向声明和参考