【问题标题】:Is it good practice to give an empty instance to a method?为方法提供空实例是一种好习惯吗?
【发布时间】:2017-02-20 15:59:45
【问题描述】:

这里是 C++ 新手!有一个类Individual 分配了很多内存,这样我们就想避免复制。让motherfather 成为两个Individuals。我希望他们使用reproduce 方法复制另一个Individual,称为baby

直观地说,我会用默认构造函数初始化baby,将它作为参数传递给reproduce,然后返回引用(尽管我认为没有必要返回引用)。这是执行此操作的代码

class Individual
{
    public:
        void reproduce (const Individual& father, Individual& baby)
        {
          // Set all attributes of baby
        }
    private:
        // Plenty of variables
}

int main()
{
  // Do Stuff
    Individual mother(arg1,arg2,arg3);
    Individual father(arg1,arg2,arg3);
    // Do stuff
    Individual baby;
    mother.reproduce(father,baby);
}

这算是好的做法吗?

另一种方法是直接在方法reproduce 中初始化baby 并返回一个引用,但我预测baby 将在reproduce 的调用结束时被销毁。

class Individual
{
    public:
        Individual& reproduce (const Individual& father)
        {
            Individual baby;
        // Set all attributes of baby
        return baby
        }
    private:
        // Plenty of variables
}

int main()
{
  // Do Stuff
    Individual mother(arg1,arg2,arg3);
    Individual father(arg1,arg2,arg3);
    // Do stuff
    auto baby = mother.reproduce(father);
}

也可以使用外部函数,但我看不出它可以代表什么优势。

【问题讨论】:

  • 关于第二种情况,返回对临时的引用是未定义的行为。
  • 为什么不直接返回一个值呢? UB 代表未定义的行为。引用所指的东西将超出范围并且在控制权返回给调用者后不存在,因此当他们使用它时,他们会得到 UB。
  • “我们希望尽可能避免复制”——您是否测量过这是一个瓶颈?不要以性能的名义不必要地使您的代码复杂化,除非您确定优化会给您带来可衡量的改进。在那之前,只需构造并返回一个临时的baby 并返回它。
  • 我会按值返回baby。与传入外部创建的婴儿相比,它是一种更好的现实模型。您可以使用 move 构造函数/赋值来使返回值便宜。
  • 请注意,从方法中按值返回对象通常不需要复制该对象,因为大多数 C++ 编译器通常实现返回值优化:en.wikipedia.org/wiki/Return_value_optimization

标签: c++ performance class methods


【解决方案1】:

成员函数reproduce应该返回婴儿。

事先生一个仅仅因繁殖行为而改变的婴儿是没有意义的——这更像是你的父母在门口发现婴儿,被鹳放在家里,然后把婴儿塑造成他们的家庭;希望您现在知道这不是它的工作原理!

不用担心性能;如果您的 Individual 类有一个移动构造函数(或遵循零规则),那么这完全不是问题。即使没有,返回值优化也应该处理好事情。

您尝试返回对局部变量的引用具有未定义的行为,正如您正确直觉的那样。所以不要这样做。

【讨论】:

  • 请注意,这实际上与“方法”无关,不可能针对特定代码段的这种特定设计问题定义“良好实践”。
  • 让我猜。关于鹳的事,爸爸妈妈也对我撒了谎。
【解决方案2】:

我会给Individual 类一个构造函数,它接受两个Individual 输入参数,例如:

class Individual
{
public:
    Individual(Individual const& mother, Individual const& father)
    {
        // Pass on traits.
    }
};

当然,除非reproduction 也用放松、羞耻或性传播疾病来修饰父母。

【讨论】:

    【解决方案3】:

    你说得对:

    另一种方法是直接在方法中初始化婴儿 复制并返回参考,但我会预测婴儿 不过会在调用reproduce结束时被销毁。

    所以第一种方法肯定比上面提到的要好。

    但是,我更喜欢(这只是我的恕我直言)使用动态分配。如果您绝对确定 reproduce 方法不会失败,并且将来也不会失败(没有人确切知道),则可以使用第一种方法。 p>

    想象一下您的代码会是什么样子?

    Individual baby;
    mother.reproduce(father,baby);
    if (baby.exists()) {
       // do stuff
    }
    

    这里的问题是您正在创建婴儿,甚至不确定它是否会被创建。我更喜欢跟随,您可以确定(如果您编写正确的代码)所有分配只会在需要时完成。

    Individual* reproduce (Individual *father)
    ...
    Individual *baby = mother->reproduce(father);
    if (baby != nullptr) {
        // do stuff
    }
    

    bool reproduce (Individual *father, Individual * &baby) // or **baby
    ...
    Individual *baby = nullptr;
    if (mother->reproduce(father, baby) {
        // do stuff
    }
    

    第二种方式更容易出错或复杂(因为你应该注意传递的baby不存在,否则覆盖它可能会导致内存泄漏)。注意:我更喜欢统一的代码(和对象的用法),所以将母亲和父亲改为指针,这不是“必须做”的事情。

    此外,通过这种方式,您甚至可以安全地将指针 baby 存储在 mother 的一些内部“子”列表中,而无需复制数据。

    附:请记住在不需要时删除婴儿

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-14
      • 1970-01-01
      • 1970-01-01
      • 2012-08-11
      相关资源
      最近更新 更多