使用引用作为依赖项的类成员 [英] Using reference as class members for dependencies

查看:19
本文介绍了使用引用作为依赖项的类成员的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在内存管理语言中花了一些时间后,我将回到 C++,但我突然有点迷失于什么是实现依赖注入的最佳方式.(我完全卖给了 DI,因为我发现它是使测试驱动设计变得非常简单的最简单方法).

现在,浏览 SO 和 google 使我对此事有很多意见,我有点困惑.

作为对这个问题的回答,C++中的依赖注入,有人建议你应该不传递原始指针,即使是依赖注入.我知道这与对象的所有权有关.

现在,在臭名昭著的谷歌风格指南中也处理了对象的所有权(虽然没有详细说明我的状态;)):http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Smart_Pointers

所以我的理解是,为了更清楚哪个对象拥有哪些其他对象的所有权,您应该避免传递原始指针.特别是,它似乎反对这种编码:

class Addict {//我依赖的东西(因此,瘾君子的名字.抱歉.)依赖*依赖_;上市:瘾君子(依赖 * 依赖):依赖_(依赖){}〜瘾君子(){//不要释放dependency_,因为它被注入并且你不拥有它!}void some_method() {依赖_->do_something();}//... 任何 ...};

如果 Dependency 是一个纯虚拟类(又名poor-man's-Interface),那么这段代码可以很容易地注入一个Dependency 的模拟版本(使用类似google mock 的东西).

问题是,我真的没有看到我可以用这种代码遇到的麻烦,以及为什么我应该想要使用原始指针以外的任何东西!是不是不清楚依赖从何而来?

另外,我读了很多帖子暗示在这种情况下确实应该使用引用,那么这种代码更好吗?

class Addict {//我依赖的东西(因此,瘾君子的名字.抱歉.)const 依赖 &依赖_;上市:瘾君子(const 依赖和依赖):dependency_(dependency) {}〜瘾君子(){//不要释放dependency_,因为它被注入并且你不拥有它!}void some_method() {依赖_.do_something();}//... 任何 ...};

但后来我得到了其他同样权威的建议反对使用参考作为成员:http://billharlan.com/pub/papers/Managing_Cpp_Objects.html

如您所见,我不太确定各种方法的相对优缺点,所以我有点困惑.如果这已经被讨论到死,我很抱歉,或者如果这只是一个给定项目中的个人选择和一致性问题......但欢迎提出任何想法.

<小时>

答案摘要

(我不知道这样做是否合适,但我会为我从答案中收集到的内容添加代码示例...)

从各种回应来看,以下是我最终可能会做的事情:

  • 将依赖项作为参考传递(至少要确保 NULL 是不可能的)
  • 在无法复制的一般情况下,明确禁止复制,并将依赖项存储为引用
  • 在可以复制的极少数情况下,将依赖项存储为 RAW 指针
  • 让依赖项的创建者(某种工厂)决定堆栈分配还是动态分配(在后一种情况下,通过智能指针进行管理)
  • 建立约定以将依赖项与自己的资源分开

所以我最终会得到类似的结果:

class NonCopyableAddict {依赖&dep_dependency_;//防止复制NonCopyableAddict &运算符 = (const NonCopyableAddict & 其他) {}NonCopyableAddict(const NonCopyableAddict & other) {}上市:NonCopyableAddict(依赖和依赖):dep_dependency_(dep_dependency){}~NonCopyableAddict() {//尝试删除对 dep_dependency_ 的引用没有风险;)}//...void do_some_stuff() {dep_dependency_.some_function();}};

对于可复制的类:

class CopyableAddict {依赖 * dep_dependency_;上市://防止复制CopyableAddict &运算符 = (const CopyableAddict & 其他) {//做任何有意义的事情...还​​是让默认操作符工作?}CopyableAddict(const CopyableAddict & 其他) {//做任何有意义的事情...}CopyableAddict(依赖和依赖):dep_dependency_(&dep_dependency){}~CopyableAddict() {//您可能想删除指针,但它的名称以 dep_ 开头,//所以按照惯例你知道这不是你的工作}//...void do_some_stuff() {dep_dependency_->some_function();}};

据我所知,没有办法表达编译器可以强制执行的我有一个指向某些东西的指针,但我不拥有它"的意图.所以我将不得不在这里诉诸命名约定......

<小时>

仅供参考

正如 Martin 所指出的,下面的例子没有解决问题.

或者,假设我有一个复制构造函数,例如:

class Addict {依赖dependency_;上市:瘾君子(const 依赖和依赖):dependency_(dependency) {}〜瘾君子(){//不要释放dependency_,因为它被注入并且你不拥有它!}void some_method() {依赖_.do_something();}//... 任何 ...};

解决方案

没有硬性规定:
正如人们所提到的,在对象内部使用引用会导致复制问题(确实如此),因此它不是万能药,但在某些情况下它可能很有用(这就是为什么 C++ 为我们提供了所有这些不同方式的选择).但是使用 RAW 指针确实不是一种选择.如果您正在动态分配对象,那么您应该始终使用智能指针维护它们,并且您的对象也应该使用智能指针.

对于需要示例的人:流总是作为引用传递和存储(因为它们不能被复制).

对您的代码示例的一些评论:

示例一和示例二

<块引用>

你的第一个带指针的例子.与使用引用的第二个示例基本相同.不同之处在于引用不能为 NULL.当您传递引用时,该对象已经处于活动状态,因此应该具有比您已经测试的对象更长的生命周期(如果它是在堆栈上创建的),因此保留引用应该是安全的.如果您动态创建指针作为依赖项,我会考虑使用 boost::shared_pointer 或 std::auto_ptr,具体取决于依赖项的所有权是否共享.

示例三:

<块引用>

我看不出你的第三个例子有什么用处.这是因为您不能使用多态类型(如果您传递从 Dependency 派生的对象,它将在复制操作期间被切片).因此,代码也可能在 Addict 内部而不是单独的类.

比尔哈伦:(http://billharlan.com/pub/papers/管理%5FCpp%5FObjects.html)

不要从比尔身上拿走任何东西但是:

  1. 我从未听说过他.
    • 他是地球物理学家,而不是计算机程序员
    • 他推荐使用 Java 编程来改进您的 C++
    • 现在语言的用法如此不同,这是完全错误的).
    • 如果您想使用要做什么/不要做什么"的参考.
      然后我会选择 C++ 领域的大人物之一:
      Stroustrup/Sutter/Alexandrescu/Meyers

总结:

  1. 不要使用 RAW 指针(当需要所有权时)
  2. 一定要使用智能指针.
  3. 不要将对象复制到您的对象中(它会切片).
  4. 您可以使用参考文献(但要了解限制).

我使用引用的依赖注入示例:

类词法分析器{公共:词法分析器(std::istream& input,std::ostream& 错误);... 东西私人的:std::istream&m_input;std::ostream&m_errors;};类解析器{公共:解析器(词法分析器和词法分析器);..... 东西私人的:Lexer&m_lexer;};int main(){CLexer 词法分析器(std::cin,std::cout);//CLexer 派生自 LexerCParser 解析器(词法分析器);//派生自 Parser 的 CParserparser.parse();}//在 test.cpp 中int main(){std::stringstream testData("XXXXXX");std::stringstream 输出;XLexer 词法分析器(测试数据,输出);XParser 解析器(词法分析器);parser.parse();}

I am going back to C++ after spending some time in memory-managed languages, and I'm suddently kinda lost as to what is the best way to implement dependency injection. (I am completely sold to DI because I found it to be the simplest way to make test-driven design very easy).

Now, browsing SO and google got me quite a number of opinions on the matter, and I'm a bit confused.

As an answer to this question, Dependency injection in C++ , someone suggested that you should not pass raw pointers around, even for dependency injection. I understand it is related to ownership of the objects.

Now, ownership of objects is also tackled (although not into enough details to my state ;) ) in the infamous google style guide : http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Smart_Pointers

So what I understand is that in order to make it clearer which object has ownership of which other objects, you should avoid passing raw pointers around. In particular, it seems to be against this kind of coding :

class Addict {
   // Something I depend on (hence, the Addict name. sorry.)
   Dependency * dependency_;
public:
   Addict(Dependency * dependency) : dependency_(dependency) {
   }
   ~Addict() {
     // Do NOT release dependency_, since it was injected and you don't own it !
   }
   void some_method() {
     dependency_->do_something();
   }
   // ... whatever ... 
};    

If Dependency is a pure virtual class (aka poor-man's-Interface ), then this code makes it easy to inject a mock version of the Dependency (using something like google mock).

The problem is, I don't really see the troubles I can get in with this kind of code, and why I should want to use anything else than raw pointers ! Is it that it is not clear where the dependency comes from?

Also, I read quite a few posts hinting that one should really be using references in this situation, so is this kind of code better?

class Addict {
   // Something I depend on (hence, the Addict name. sorry.)
   const Dependency & dependency_;
  public:
   Addict(const Dependency & dependency) : dependency_(dependency) {
   }
   ~Addict() {
     // Do NOT release dependency_, since it was injected and you don't own it !
   }
   void some_method() {
     dependency_.do_something();
   }
   // ... whatever ... 
};

But then I get other, equally authoritive advices against using references as member: http://billharlan.com/pub/papers/Managing_Cpp_Objects.html

As you can see I am not exactly sure about the relative pros and cons of the various approaches, so I am a bit confused. I am sorry if this has been discussed to death, or if it is only a matter of personnal choice and consistency inside a given project ... but any idea is welcome.


Answers summary

(I don't know if it is good SO-tiquette to do this, but I'll add code example for what I gathered from answers...)

From the various responses, here's what I'll probably end up doing in my case:

  • pass dependencies as reference (at least to make sure NULL is not possible)
  • in the general case where copying is not possible, explicitly disallow it, and store dependencies as reference
  • in the rarer case where copying is possible, store dependencies as RAW pointers
  • let the creator of the dependencies (factory of some kind) decide between stack allocation or dynamic allocation (and in the latter case, management through a smart pointer)
  • establish a convention to separate dependencies from own resources

So I would end up with something like:

class NonCopyableAddict {
    Dependency & dep_dependency_;

    // Prevent copying
    NonCopyableAddict & operator = (const NonCopyableAddict & other) {}
    NonCopyableAddict(const NonCopyableAddict & other) {}

public:
    NonCopyableAddict(Dependency & dependency) : dep_dependency_(dep_dependency) {
    }
    ~NonCopyableAddict() {
      // No risk to try and delete the reference to dep_dependency_ ;)
    }
    //...
    void do_some_stuff() {
      dep_dependency_.some_function();
    }
};

And for a copyable class:

class CopyableAddict {
    Dependency * dep_dependency_;

public: 
    // Prevent copying
    CopyableAddict & operator = (const CopyableAddict & other) {
       // Do whatever makes sense ... or let the default operator work ? 
    }
    CopyableAddict(const CopyableAddict & other) {
       // Do whatever makes sense ...
    }


    CopyableAddict(Dependency & dependency) : dep_dependency_(&dep_dependency) {
    }
    ~CopyableAddict() {
      // You might be tempted to delete the pointer, but its name starts with dep_, 
      // so by convention you know it is not your job
    }
    //...
    void do_some_stuff() {
      dep_dependency_->some_function();
    }
};

From what I understood, there is no way to express the intent of "I have a pointer to some stuff, but I don't own it" that the compiler can enforce. So I'll have to resort to naming convention here...


Kept for reference

As pointed out by Martin, the following example does not solve the problem.

Or, assuming I have a copy constructor, something like:

class Addict {
   Dependency dependency_;
  public:
   Addict(const Dependency & dependency) : dependency_(dependency) {
   }
   ~Addict() {
     // Do NOT release dependency_, since it was injected and you don't own it !
   }
   void some_method() {
     dependency_.do_something();
   }
   // ... whatever ... 
};

解决方案

There is no hard and fast rule:
As people have mentioned using references inside objects can cause copy problems (and it does) so it is not a panacea, but for certain situation it can be useful (that is why C++ gives us the option to do it all these different ways). But using RAW pointers is really not an option. If you are dynamically allocating objects then you should always be maintaining them with smart pointers and your object should also be using smart pointers.

For people who demand examples: Streams are always passed and stored as references (as they can't be copied).

Some Comments on your code examples:

Example one and two

Your first example with pointers. Is basically the same as the second example using references. The difference being that a reference can not be NULL. When you pass a reference the object is already alive and thus should have a lifespan greater than the object you are testing already (If it was created on the stack) so it should be safe to keep a reference. If you are dynamically creating pointers as dependencies I would consider using boost::shared_pointer or std::auto_ptr depending if ownership of the dependency is shared or not.

Example Three:

I don't see any great use for your third example. This is because you can not use polymorphic types (If you pass an object derived from Dependency it will be sliced during the copy operation). Thus the code may as well be inside Addict rather than a separate class.

Bill Harlen: (http://billharlan.com/pub/papers/Managing%5FCpp%5FObjects.html)

Not to take anything away from Bill But:

  1. I have never heard of him.
    • He is a Geo-Physists not a computer programmer
    • He recomends programming in Java to improve your C++
    • The languages are now so different in usage that is utterly false).
    • If you want to use references of What to-do/not to-do.
      Then I would pick one of the Big names in the C++ field:
      Stroustrup/Sutter/Alexandrescu/Meyers

Summary:

  1. Don't use RAW pointers (when ownership is required)
  2. Do use smart pointers.
  3. Don't copy objects into your object (it will slice).
  4. You can use references (but know the limitations).

My example of Dependency injection using references:

class Lexer
{
    public: Lexer(std::istream& input,std::ostream& errors);
    ... STUFF
    private:
       std::istream&  m_input;
       std::ostream&  m_errors;
};
class Parser
{
    public: Parser(Lexer& lexer);
    ..... STUFF
    private:
        Lexer&        m_lexer;
};

int main()
{
     CLexer  lexer(std::cin,std::cout);  // CLexer derived from Lexer
     CParser parser(lexer);              // CParser derived from Parser

     parser.parse();
}

// In test.cpp
int main()
{
     std::stringstream  testData("XXXXXX");
     std::stringstream  output;
     XLexer  lexer(testData,output);
     XParser parser(lexer);

     parser.parse();
}

这篇关于使用引用作为依赖项的类成员的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆