总是让我的对象成员的数据类型成为引用是不好的做法吗?
阅读此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;
}
答案 0 :(得分:1)
总是让我的对象成员的数据类型成为引用是不好的做法吗?
不仅是不好的做法,而且效率低,限制性强。
为什么?
包含引用的类的对象是自动不可分配的。该标准有std :: reference_wrapper&lt;&gt; (基本上是一个包装指针)在将引用类型绑定到函数对象时绕过此限制。
您有义务管理对象的生命周期。由于您要存储引用而不是对象,因此您的类不可能自行清理。此外,它无法确保它所引用的对象仍然存在。你刚刚失去了c ++给你的所有保护。欢迎来到石器时代。
效率较低。现在所有对象访问都通过本质上是一个指针来解除引用。而你最有可能失去缓存局部性。没有意义的。
您失去了将对象视为值的能力。这将导致效率低下,错综复杂的计划。
我可以继续......
我该怎么办?
首选值语义(将对象视为值)。让他们被复制(如果你很好地编写你的课程,大多数副本都会被删除)。将它们封装并组合成更大的对象。让容器管理组件的生命周期。这是它的工作。
包含一些头文件的时间成本怎么样?
除非您在数百(数千)个模板类中进行可怕的复杂模板扩展,否则它甚至不太可能成为问题。制作咖啡的时间比编写一些源文件要多。
答案 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;
}
包含顺序稍有变化,您可以省略前向声明和参考