2016-03-09 2 views
1

Неправильно ли всегда использовать тип данных моего объекта-члена?Должны ли все объекты класса быть ссылкой?

После прочтения этого вопроса question about avoiding #include Я пытался избежать использования #include в моем текущем проекте на C++ и всегда пересылать объявления своих типов данных.

Вот пример фрагмента кода из моего проекта. В заголовке у меня есть 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

Вы не понимаете ссылочный тип (например, «EnemyBuilder &») и прямую ссылку (например, «класс Enemy»). Совершенно разные вещи –

+1

У вашего кода есть главный недостаток: 'm_builder (EnemyBuilder()) создает ссылку на временную ... – kfsone

+0

Какую проблему вы пытаетесь решить? –

ответ

1

Неправильно ли всегда использовать тип данных моего объекта-члена?

Не только это плохая практика, она неэффективна и ограничительна.

Почему?

  1. Объекты классов, которые содержат ссылки автоматически не подлежит переуступке. Стандарт имеет std :: reference_wrapper <> (в основном обернутый указатель), чтобы обойти это ограничение при привязке ссылочных типов к функциям объектов.

  2. Вы обязаны управлять сроками жизни объектов. Поскольку вы храните ссылки, а не объекты, ваш класс не имеет возможности очищать после себя. Кроме того, он не может гарантировать, что объекты, к которым он ссылается, все еще существуют. Вы только что потеряли всю защиту, которую дает C++. Добро пожаловать в каменный век.

  3. Это менее эффективно. Все доступ к объектам теперь разыменовывается через то, что по существу является указателем. И вы, скорее всего, потеряете локальную сеть. Бессмысленно.

  4. Вы теряете способность рассматривать объекты как значения. Это приведет к менее эффективным, запутанным программам.

  5. Я мог бы пойти на ...

Что я должен делать?

Предпочитает значение-семантику (обработка объектов как значений). Пусть они будут скопированы (большинство копий будет отменено, если вы хорошо напишете свои классы). Инкапсулируйте и составьте их в более крупные объекты. Пусть контейнер управляет временем жизни компонентов. Это его работа.

Как насчет временной стоимости включения нескольких файлов заголовков?

Если вы не делаете отвратительно сложное расширение шаблона на сотнях (тысячах) классов шаблонов, это вряд ли даже приблизится к проблеме. Вы тратите больше времени на кофе, чем на компиляцию нескольких исходных файлов.

+0

Обычно правильный ответ, но в нем упоминается согласованность кеша в несвязанном контексте. Вы не можете потерять его (или приобрести), это свойство вашей архитектуры. – SergeyA

+0

@SergeyA Да, было уже поздно, и я не доволен формой слов, которые я придумал. –

+0

Ну, вы можете просто пойти и отредактировать его. Я считаю, что вы хотели сказать, это «местность кеша». – SergeyA

-1

Я думаю, что вы убиваете муравьев с молотком. форвардная декларация является инструментом, но не всегда является «лучшей практикой». Вы должны подумать, что кто-то (или вы) читает ваш код в будущем, они должны понять, что делает ваш код.

Я предлагаю вам этот подход:

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; 
} 

Небольшое изменение в включают заказ, и вы могли бы ommit вперед декларации и ссылки

+0

Наилучший подход заключается в том, чтобы получить файл заголовка для компиляции с максимально возможным количеством '# include'. –

0

См. Richard Hodges' answer ... В вашем текущем c ода, ты собираешься бочонков пороха для себя, вы можете как-то разжечь его ... когда-нибудь скоро ... Чтобы исправить это.

В 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.каст

#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 :: shared_ptr`.

Смежные вопросы