C++ 使用引用作为依赖项的类成员

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1974682/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 21:44:33  来源:igfitidea点击:

Using reference as class members for dependencies

c++dependency-injection

提问by phtrivier

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).

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

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

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

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.

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

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

现在,在臭名昭著的谷歌风格指南中也处理了对象的所有权(虽然没有足够详细地说明我的状态;)):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).

如果 Dependency 是一个纯虚拟类(又名poor-man's-Interface),那么这段代码可以很容易地注入一个Dependency 的模拟版本(使用类似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 againstusing references as member: http://billharlan.com/pub/papers/Managing_Cpp_Objects.html

但后来我得到了其他同样权威的建议,反对使用引用作为成员: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
  • 传递依赖项作为参考(至少要确保 NULL 是不可能的)
  • 在不可能复制的一般情况下,明确禁止它,并将依赖项存储为引用
  • 在可以复制的极少数情况下,将依赖项存储为 RAW 指针
  • 让依赖项的创建者(某种工厂)决定堆栈分配还是动态分配(在后一种情况下,通过智能指针进行管理)
  • 建立一个约定来将依赖项与自己的资源分开

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.

正如 Martin 所指出的,以下示例无法解决问题。

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

采纳答案by Martin York

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.

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

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.

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

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.

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

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

比尔哈伦:(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
  1. 我从来没有听说过他。
    • 他是地球物理学家而不是计算机程序员
    • 他建议使用 Java 编程以改进您的 C++
    • 这些语言现在在用法上如此不同,这是完全错误的)。
    • 如果您想使用“做什么/不做什么”的参考。
      然后我会选择 C++ 领域的大牌之一:
      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).
  1. 不要使用 RAW 指针(需要所有权时)
  2. 一定要使用智能指针。
  3. 不要将对象复制到您的对象中(它会切片)。
  4. 您可以使用参考文献(但要了解局限性)。

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

回答by JoeG

Summary:If you need to store a reference, store a pointer as a private variable and access it through a method which dereferences it. You can stick a check that the pointer isn't null in the object's invariant.

总结:如果需要存储引用,请将指针存储为私有变量,并通过取消引用它的方法访问它。您可以检查对象的不变量中的指针是否为空。

In depth:

深入:

Firstly, storing references in classes makes it impossible to implement a sensible and legal copy constructor or assignment operator, so they should be avoided. It is usually a mistake to use one.

首先,在类中存储引用使得无法实现合理合法的复制构造函数或赋值运算符,因此应该避免它们。使用一个通常是错误的。

Secondly, the type of pointer/reference passed in to functions and constructors should indicate who has responsibility for freeing the object and how it should be freed:

其次,传递给函数和构造函数的指针/引用的类型应该表明谁负责释放对象以及应该如何释放它:

  • std::auto_ptr - the called function is responsible for freeing, and will do so automatically when it's done. If you need copy semantics, the interface must provide a clone method which should return an auto_ptr.

  • std::shared_ptr - the called function is responsible for freeing, and will do so automatically when it's done and when all other references to the object are gone. If you need shallow copy semantics the compiler generated functions will be fine, if you need deep copying the interface must provide a clone method which should return a shared_ptr.

  • A reference - the caller has responsibility. You don't care - the object may be stack allocated for all you know. In this case you should pass by reference but store by pointer. If you need shallow copy semantics the compiler generated functions will be fine, if you need deep copying you're in trouble.

  • A raw pointer. Who knows? Could be allocated anywhere. Could be null. You might be responsible for freeing it, you might not.

  • Any other smart pointer - it should manage the lifetime for you, but you'll need to look at the documentation to see what the requirements are for copying.

  • std::auto_ptr - 被调用的函数负责释放,完成后会自动释放。如果你需要复制语义,接口必须提供一个克隆方法,它应该返回一个 auto_ptr。

  • std::shared_ptr - 被调用的函数负责释放,并在它完成时以及对对象的所有其他引用都消失时自动释放。如果您需要浅拷贝语义,编译器生成的函数就可以了,如果您需要深拷贝,接口必须提供一个克隆方法,该方法应该返回一个 shared_ptr。

  • 参考 - 调用者有责任。你不在乎 - 对象可能是你知道的堆栈分配的。在这种情况下,您应该通过引用传递但通过指针存储。如果你需要浅拷贝语义,编译器生成的函数就可以了,如果你需要深拷贝,你就有麻烦了。

  • 一个原始指针。谁知道?可以分配到任何地方。可能为空。您可能负责释放它,也可能不负责。

  • 任何其他智能指针 - 它应该为您管理生命周期,但您需要查看文档以了解复制的要求。

Note that the methods which give you responsibility for freeing the object don't break DI - freeing the object is simply a part of the contract you have with the interface (as you don't need to know anything about the concrete type to free it).

请注意,让您负责释放对象的方法不会破坏 DI - 释放对象只是您与接口签订的合同的一部分(因为您不需要了解有关具体类型的任何信息来释放它)。

回答by peterchen

[update 1]
If you can always guarantee the dependency outlives the addict, you canuse a raw pointer/reference, of course. between these two, I'd make a very simple decision: pointer if NULL is allowed, reference otherwise.

[更新 1]
如果你总能保证依赖比瘾君子还长,你当然可以使用原始指针/引用。在这两者之间,我会做出一个非常简单的决定:如果允许为 NULL,则为指针,否则为引用。

(The point of my original post was that neither pointer nor reference solve the lifetime problem)

(我原来的帖子的重点是指针和引用都不能解决生命周期问题)



I'd follow the infamous google style guideline here, and use smart pointers.

我会在这里遵循臭名昭著的谷歌风格指南,并使用智能指针。

Both a pointer and a reference have the same problem: you need to make sure the dependency outlives the addict. That pushes a quite nasty responsibility onto the client.

指针和引用都有相同的问题:你需要确保依赖比上瘾者更持久。这将相当恶劣的责任推给了客户。

With a (reference-counted) smart pointer, the policy becomes dependency is destroyed when noone uses it anymore. Sounds perfect to me.

使用(引用计数)智能指针,当没有人再使用它时,策略变成依赖关系被破坏。对我来说听起来很完美。

Even better: with boost::shared_ptr(or a similar smart pointer that allows a type-neutral destruction policy) the policy is attached to the object at construction- which usually means everything affecting the dependency ends up in a single place.

更好的是:使用boost::shared_ptr(或类似的允许类型中性销毁策略的智能指针)策略在构造时附加到对象- 这通常意味着影响依赖项的所有内容最终都在一个地方。

The typical problems of smart pointers - overhead and circular references - rarely come into play here. Dependency instances usually aren't tiny and numerous, and a dependency that has a strong reference back to its addicts is at least a code smell. (still, you need to keep these things in mind. Welcome back to C++)

智能指针的典型问题——开销和循环引用——很少在这里发挥作用。依赖实例通常不是小而多的,并且对其上瘾者有很强的引用的依赖至少是一种代码味道。(仍然,你需要记住这些事情。欢迎回到 C++)

Warning: I am not "totally sold" to DI, but I'm totally sold on smart pointers ;)

警告:我并没有“完全出售”给 DI,但我完全依赖智能指针 ;)

[update 2]
Note that you can always create a shared_ptrto a stack/global instance using a null deleter.
This requires both sides to support this, though: addict must make guarantees that it will not transfer a reference to the dependency to someone else who might live longer, and caller is back with the responsibility ensuring lifetime. I am not to happy with this solution, but have used this on occasion.

[更新 2]
请注意,您始终可以shared_ptr使用空删除器创建堆栈/全局实例。
但是,这需要双方支持这一点:瘾君子必须保证它不会将依赖项的引用转移给可能活得更久的其他人,并且调用者有责任确保生命周期。我对这个解决方案不满意,但有时会使用它。

回答by D.Shawley

I would steer clear of references as members since they tend to cause no end of headaches if you end up sticking one of your objects in an STL container. I would look into using a combination of boost::shared_ptrfor ownership and boost::weak_ptrfor dependents.

我会避开作为成员的引用,因为如果您最终将一个对象粘在 STL 容器中,它们往往会导致无休止的头痛。我会考虑使用boost::shared_ptr所有权和boost::weak_ptr家属的组合。

回答by D.Shawley

It has been asked before, but my SO search skills are not up to finding it. To summarise my position - you should very rarely, if ever, use references as class members. Doing so causes all sorts of initialisation, assignment and copying problems. Instead, use a pointer or a value.

之前有人问过,但我的 SO 搜索技能无法找到它。总结一下我的立场——你应该很少(如果有的话)使用引用作为类成员。这样做会导致各种初始化、分配和复制问题。相反,使用指针或值。

Edit:Found one - this is a question with a variety of opinions as answers: Should I prefer pointers or references in member data?

编辑:找到一个 - 这是一个有多种意见的问题作为答案:我应该更喜欢成员数据中的指针还是引用?

回答by DaMacc

But then I get other, equally authoritive advices against using references as member : http://billharlan.com/pub/papers/Managing%5FCpp%5FObjects.html

但后来我得到了其他同样权威的建议,反对使用引用作为成员:http: //billharlan.com/pub/papers/Managing%5FCpp%5FObjects.html

In this case I think you only want to set the object once, in the constructor, and never change it so no problem. But if you want to change it later on, use an init function, have a copy constructor, in short everything that would have to change the reference you will have to use pointers.

在这种情况下,我认为您只想在构造函数中设置对象一次,并且永远不要更改它,所以没问题。但是如果你以后想改变它,使用一个 init 函数,有一个复制构造函数,简而言之,所有需要改变引用的东西都必须使用指针。

回答by Charles Eli Cheese

I can here my downmoderation coming already, but I will say that there should be no reference members in a class FOR ANY REASO, EVER. Except if they are a simple constant value. The reasons for this are many, the second you start this you open up all the bad things in C++. See my blog if you really care.

我可以在这里进行我的降级,但我会说,无论出于任何原因,类中都不应该有参考成员,永远。除非它们是简单的常量值。造成这种情况的原因有很多,一旦你开始这样做,你就会打开 C++ 中所有不好的东西。如果你真的关心,请看我的博客。