【问题标题】:From C#/Java to C++: Understanding C++ references in an example?从 C#/Java 到 C++:在示例中理解 C++ 引用?
【发布时间】:2011-12-15 11:19:47
【问题描述】:

来自 C#/Java 背景,我正在尝试了解使用 C++ 指针和引用的最佳实践。我敢肯定这个网站上已经报道了这个问题,但我仍然不完全理解。我已经阅读了一些 C++ 常见问题解答,但我需要在上下文中查看它。假设我有两个类:

class Employee
{
  Employee();
  ~Employee();
}


class Company
{
   Company();
   ~Company();

   AddEmployee(?? employee);
   ?? GetEmployee();

private:
   std::list<Employee??> employees_;
}

AddEmployee 获取一个员工对象并将其添加到员工的私有列表中。 AddEmployee 员工参数的类型应该是什么?

AddEmployee(Employee *employee);
AddEmployee(Employee& employee);

私有 std::list 应该是什么模板类型?

还假设公司类可以修改员工const这里合适吗?并且如果我使用引用参数,Company类是否可以转身将员工对象传递给另一个类?

更新

我看到很多人建议使用值语义,但来自 C#/java 的这很有挑战性。通常我使用接口进行依赖注入,我不确定在这种情况下是否可以使用值语义。您可以使用值语义对接口进行编码吗?

在我现在正在处理的代码中,几乎所有对象都分配在堆上,我的成员函数看起来像void TakeSomeObject(SomeObject *someObject)MyClass(SomeService *service)。尽管随着代码变得越来越复杂,我也担心内存泄漏,但代码仍然有效。我想知道是否应该将这些方法签名改为使用引用,以及这样做的含义是什么。

此外,当我阅读有关智能指针的信息时,我想检查我的代码并将大多数指针更改为 shared_ptr,但我不清楚这是否是一件好事。在那种情况下,我是否只需更改所有方法以将 shared_ptr 作为参数?

更新 2

似乎共识是使用AddEmployee(const Employee&amp; employee)AddEmployee(Employee&amp; employee) 来分配堆分配的员工。面对强有力的证据,我愿意改变,但我不认为自己现在正在编写值语义版本:AddEmployee(Employee employee)

更新 3

我试着写一个方法,比如:

AddEmployee(const Employee& employee)
{
  employees_.push_back(??employee);
}

但是,这似乎无法尝试替换 ??当列表定义为std::list&lt;Employee*&gt; employees_ 时,没有任何内容、& 或 *。这是否意味着我必须采用指针参数而不是引用参数才能执行此类操作?

【问题讨论】:

  • 你了解 C 中的指针吗?
  • 好的,如果您了解指针,那么您可以问自己一些类似的问题:employees_ 成员是否适合作为指针容器或对象容器?您的方法应该采用指针还是按值传递对象?
  • 我更喜欢它是一个指针容器,因为我更喜欢 C#/java 的引用语义而不是 C++ 值语义。

标签: c++ pointers reference


【解决方案1】:

AddEmployee 员工参数的类型应该是什么?

参数的类型应该是Employee const&amp;,即对雇员的常量引用。这是为了避免将副本作为参数参数,并指定不会从函数内部修改引用的对象。这反过来又允许使用临时值调用函数。

私有 std::list 应该是什么模板类型?

列表的类型应该是std::list&lt; Employee &gt;,而class Company 将是此类Employee 对象的所有者(实际上list 将是)。如果您正在查看更复杂的所有权关系,例如通常在 C# 中找到的所有权关系,您最好使用智能指针容器(或 Boost 中的指针容器)。

【讨论】:

  • 在实现智能指针容器之前请仔细考虑。 STL 集装箱倾向于假设它们所装的东西表现得像惰性货物,而不是挑剔的乘客。
  • @Beta:我认为在 STL 容器中使用智能指针没有问题。 AFAIK,STL 容器只想要具有合理复制语义的东西(所以不是std::auto_ptr,尽管我不认为这是“智能”)。
  • @jamesdlin:我同意合理的复制语义,但这正是许多通常称为“智能指针”(包括auto_ptr)的东西所缺乏的。请注意此类术语的非标准定义,即使您的定义更好:“Il est dangereux d'avoir raison dans des chooses où des hommes accrédités ont tort。”.
【解决方案2】:

前两部分的答案(关于AddEmployeeGetEmployee 部分取决于复制Employee 的伤害程度。它是否可以复制?如果不能,那么您必须通过引用传递它。

如果它是可复制的,你希望人们轻松地复制它吗?它只是一小部分简单类型,还是包含较重的对象(字符串、数组等)?如果它相当庞大,那么您希望尽可能少地复制,因此通过引用传递是合理的。

现在对于AddEmployee,真的没有理由不把它当作const&amp; 的值。您将不得不将其复制到std::list 中(稍后会详细介绍),因此将其作为常量参考并没有真正的问题。这可以是const Employee &amp;Employee const &amp;;任你选,它们的意思是一样的。

对于GetEmployee,您应该问自己一个问题:我希望用户用它做什么?例如,在某些情况下,您希望用户能够更改存储在std::list 中的实际Employee 对象。现在,我认为这有点可疑,或者至少值得以某种形式解释为什么您需要将实际的 Employee 对象提供给某人以进行更改,但返回引用是相当普遍的。

如果这样做,您还应该提供GetEmployeeconst 版本,它返回const&amp;。像这样:

Employee &GetEmployee();
const Employee &GetEmployee() const;

这样,如果他们有const Company,他们可以获得const Employee,他们可以查看,但不能修改

至于std::list,这真的很简单:您从不存储引用容器。 永远。您的选项是值或指针。实际上,您的选项确实应该是值或 smart 指针(查找这些)。由于我们在这里不处理智能指针,我们应该只使用值。

所以我建议这样做:

class Company
{
   Company();
   ~Company();

   AddEmployee(const Employee &employee);
   const Employee &GetEmployee() const;
   Employee &GetEmployee();

private:
   std::list<Employee> employees_;
}

【讨论】:

  • 您没有发现返回对 list 元素的引用有任何问题?
  • @Beta:你读过我说的那部分吗,“现在同意,我认为这有点可疑,或者至少值得以某种形式解释为什么需要提供实际的 Employee 对象给某人改变”?此外,lists 很少使他们的成员失效,所以这并不是非常危险。这肯定比在 vectordeque 中返回元素更危险。
  • 我确实阅读了该部分,并且我同意它很少会导致错误...一种罕见的、晦涩难懂的错误会导致未定义的行为。
【解决方案3】:

我认为这是一个品味问题,你应该做两个签名。

然而,对于大多数初学者来说,员工列表应该是一个实际实例的列表,而不是指针,它不能是引用

您应该很少保留指针列表,因为除非您知道自己在做什么,否则您很可能会因不释放或释放过于频繁而导致内存管理问题——C++ 不会容忍内存分配错误。

class Company
{
   Company();
   ~Company();

   AddEmployee(const Employee& employee) { employee_.push(employee); }
   AddEmployee(const Employee* employee) { employee_.push(*employee); }
   const Employee& GetEmployee() const;
   Employee& GetEmployee();

private:
   std::list<Employee> employees_;
}

通过这种方式,您可以在内部维护一份员工副本列表,并且该接口允许拥有指针和实例的人通过引用来推送它。

我一般会避免分发指向内部数据结构的指针,因此 GetEmployee 最多应该分发一个引用(这也是一种指针,但不同)——在这里你可能需要访问器——一个用于 const,一个用于非 const 上下文。

【讨论】:

    【解决方案4】:

    这是Employees 的列表:

    std::list<Employee> employees_;
    

    你可以有一个指向Employees 的指针列表,像这样

    std::list<Employee *> employees_;
    

    但是你必须在堆上管理实际的Employees(指针指向的东西)。有时是个好主意,但在这种情况下可能只是一种痛苦。

    这个:

    Employee GetEmployee();
    

    返回一个Employee,一个值,一个对象,可能是列表中某事物的副本。您可以使用以下功能之一:

    Employee & GetEmployee();
    Employee * GetEmployee();
    

    返回一个引用或指针(分别)到一个实际的员工。在这种情况下,前者是一个坏主意,因为std::list 不承诺保证其内容安全以供参考;它可能会复制Employees 之一,保留它并删除原件。后者也有同样的问题,但如果你使用std::list&lt;Employee *&gt; 会稍微安全一些,因为列表会洗牌指针,但不处理对象——但在这种情况下,没有处理堆责任的好方法:它Company 构造 Employees 是有意义的,所以它也可以删除它们,但是任何称为 GetEmployee 的东西都可能会以一个失效的指针结束。

    以下任何一种都可以:

    AddEmployee(Employee employee);
    AddEmployee(Employee & employee);
    

    第二个稍微好一点,因为它避免了不必要的复制。无论哪种方式,原件的副本都会出现在列表中。不要使用AddEmployee(Employee * employee);这是完全合法的,但你只是通过传递一个无效的指针来增加导致错误的机会。

    【讨论】:

      【解决方案5】:

      AddEmployee 员工参数的类型应该是什么?

      取决于你想做什么。如果你想放弃分配对象的所有权,那么你应该使用指针。但是,指针不能保证被分配或有效,因此接纳这种类型的员工风险更大。

      // Example where you give up ownership
      Employee* employee = new Employee();
      
      // Call the method taking in a pointer to an employee
      AddEmployee(employee);
      
      // You allocated it, but you didn't delete it,
      // so whoever owns employee now will have to delete it.
      employee = NULL; 
      

      你也可以是坏事:

      // Example where you give up ownership
      Employee* employee = NULL;
      
      // Oops, you're passing in a null pointer.
      AddEmployee(employee);
      
      // Allocate an employee
      employee = new Employee(); 
      delete employee;
      
      // Oops, you're passing in an deallocated object which is 
      // the worst case scenario because the behavior is undefined.
      AddEmployee(employee);
      

      引用Employee 对象更安全,因为该语言不允许您传递空引用:换句话说,该引用保证被分配且非空。建议您在有意义的情况下使用引用而不是指针。

      AddEmployee(Employee& employee);
      

      私有 std::list 应该是什么模板类型?

      更新:我现在收到您的问题,答案很复杂:尽管对于您的具体情况,最好有指向 Employee 的指针,因为它们很可能会长期存在对象,您不想将它们存储在堆栈中。所以对于你的情况,答案是list&lt;Employee*&gt;。但是,这里有几个问题解决了决定何时使用堆栈分配和堆分配所涉及的许多细微差别:


      还假设 company 类可以修改员工,这里是 const 合适吗?

      如果你使用引用并且不复制对象,那么你不能将它作为const 传入并修改它。如果您使用指针,那么您将进入const correctness,您可以在此处阅读更多相关信息:http://www.parashift.com/c++-faq-lite/const-correctness.html


      如果我使用引用参数,Company类是否可以转身将员工对象传递给另一个类?

      是的。

      【讨论】:

      • 重新回答“私有 std::list 应该是什么模板类型?”如果我理解正确,我不同意。在 C# 中,我将私有列表定义为 IList&lt;Employee&gt;。但是在 C# 中,这将是在堆上分配的员工对象的引用列表。在 C++ 中,std::list&lt;Employee&gt; 正在创建一个值对象列表(也许是在堆栈上创建的?)。或者,我可以做std::list&lt;Employee *&gt;,这对我来说更接近 C# 语义。对我来说,问题的症结在于我应该做什么并不明显。我更喜欢指针语义,因为它更接近 C#。
      • @User,那么我必须道歉,因为我没有正确理解你:)...我以为你根本不知道要列出什么模板类型(即list&lt;Employee&gt; ) 而现在我一想到你不知道这一点就感到尴尬,如果你不知道的话。
      • 不用担心,谢谢您的回答。我很想在任何地方都使用 shared_ptr。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-20
      • 2013-04-20
      • 2023-02-06
      • 1970-01-01
      • 2020-11-21
      • 1970-01-01
      相关资源
      最近更新 更多