class 中的所有 objects 都应该是参考吗?
Should all objects in a class be a reference?
总是将我的 object 成员的数据类型作为引用是一种不好的做法吗?
阅读这篇文章后 question about avoiding #include 我一直在努力避免在我当前的 C++ 项目中使用 #include 并始终向前声明我的数据类型。
这是我项目中的示例代码片段。在 header 我有 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;
}
我觉得你是在用锤子打蚂蚁。前向声明是一种工具,但并不总是一种"best practice"。您必须考虑将来有人(或您)阅读您的代码,他们必须了解您的代码在做什么。
我建议你这个方法:
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;
}
包含顺序略有变化,您可以省略前向声明和引用
请先查看 ...在您当前的代码中,您正在为自己堆积 桶火药,您可能有一天会点燃它.... ...很快有一天... 补救。
In EnemyFactory.h, you have a line... Prefer to use
std::unique_ptr
Enemy* produce(std::string type);
So that, this becomes
std::unique_ptr<Enemy> produce(std::string type);
再次 EnemyFactory.cpp...
You have the dreaded Undefined Behaviour, basically, your program
will crash because you binded a reference to a temporary object
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;
}
我们这里没有使用引用,因为所有 class 引用成员都必须在构造函数中实例化,管理生命周期可能会很麻烦,不过取决于您的用例....
指针在这里更方便....记住,每当您想到指针时,请先选择 std::unique_ptr
s,然后在共享对象时,您可以选择 'std::shared_ptr`。
Is it bad practice to always make my object member's data type a reference?
这不仅是不好的做法,而且效率低且限制性强。
Why?
包含引用的 classes 的对象自动不可分配。当将引用类型绑定到函数对象时,标准有 std::reference_wrapper<>(基本上是一个包装指针)来绕过这个限制。
您有义务管理对象的生命周期。由于您存储的是引用而不是对象,因此您的 class 无法自行清理。此外,它不能确保它所引用的对象仍然存在。你刚刚失去了 c++ 给你的所有保护。欢迎来到石器时代。
效率较低。现在,所有对象访问都通过本质上是指针的方式取消引用。而且您很可能会丢失缓存位置。毫无意义。
你失去了将对象视为值的能力。这将导致程序效率低下、复杂。
我可以继续...
What should I do?
更喜欢值语义(将对象视为值)。让它们被复制(如果你的 classes 写得很好,大部分副本都会被删除)。将它们封装并组合成更大的对象。让容器管理组件的生命周期。这是它的工作。
What about the time cost of including a few header files?
除非您在成百(上千)个模板 class 中进行极其复杂的模板扩展,否则它甚至不太可能成为问题。泡咖啡浪费的时间比编译几个源文件的时间还多。
总是将我的 object 成员的数据类型作为引用是一种不好的做法吗?
阅读这篇文章后 question about avoiding #include 我一直在努力避免在我当前的 C++ 项目中使用 #include 并始终向前声明我的数据类型。
这是我项目中的示例代码片段。在 header 我有 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;
}
我觉得你是在用锤子打蚂蚁。前向声明是一种工具,但并不总是一种"best practice"。您必须考虑将来有人(或您)阅读您的代码,他们必须了解您的代码在做什么。
我建议你这个方法:
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;
}
包含顺序略有变化,您可以省略前向声明和引用
请先查看
In EnemyFactory.h, you have a line... Prefer to use
std::unique_ptr
Enemy* produce(std::string type);
So that, this becomes
std::unique_ptr<Enemy> produce(std::string type);
再次 EnemyFactory.cpp...
You have the dreaded Undefined Behaviour, basically, your program will crash because you binded a reference to a temporary object
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;
}
我们这里没有使用引用,因为所有 class 引用成员都必须在构造函数中实例化,管理生命周期可能会很麻烦,不过取决于您的用例....
指针在这里更方便....记住,每当您想到指针时,请先选择 std::unique_ptr
s,然后在共享对象时,您可以选择 'std::shared_ptr`。
Is it bad practice to always make my object member's data type a reference?
这不仅是不好的做法,而且效率低且限制性强。
Why?
包含引用的 classes 的对象自动不可分配。当将引用类型绑定到函数对象时,标准有 std::reference_wrapper<>(基本上是一个包装指针)来绕过这个限制。
您有义务管理对象的生命周期。由于您存储的是引用而不是对象,因此您的 class 无法自行清理。此外,它不能确保它所引用的对象仍然存在。你刚刚失去了 c++ 给你的所有保护。欢迎来到石器时代。
效率较低。现在,所有对象访问都通过本质上是指针的方式取消引用。而且您很可能会丢失缓存位置。毫无意义。
你失去了将对象视为值的能力。这将导致程序效率低下、复杂。
我可以继续...
What should I do?
更喜欢值语义(将对象视为值)。让它们被复制(如果你的 classes 写得很好,大部分副本都会被删除)。将它们封装并组合成更大的对象。让容器管理组件的生命周期。这是它的工作。
What about the time cost of including a few header files?
除非您在成百(上千)个模板 class 中进行极其复杂的模板扩展,否则它甚至不太可能成为问题。泡咖啡浪费的时间比编译几个源文件的时间还多。