【问题标题】:Is this good or bad practice with dynamic memory allocation?这是动态内存分配的好做法还是坏做法?
【发布时间】:2021-07-06 10:58:27
【问题描述】:

我见过其他人使用它,它看起来很聪明,但我不确定这是好还是坏的做法。它可以工作,而且我个人喜欢它的工作方式,但是在更大的程序范围内这样做真的有用吗?

他们所做的是在实际函数参数中动态分配一些数据类型,并在函数中删除它。这是一个例子:

#include <iostream>

class Foo {
private:
    int number;
public:
    Foo(int n) : number(n) { }
    int num() { return number; }
    Foo* new_num (int i) { number = i; }
};

void some_func (int thing, Foo* foo);

int main() {
    std::cout << "Enter number: ";
    int n;
    std::cin >> n;
    some_func(n, new Foo(0)); // <-- uses the 'new' operator with a function argument
    return 0;
}

// calculates difference between 'thing' and 'n'
// then puts it inside the Foo object
void some_func (int thing, Foo* foo) {
    std::cout << "Enter another number: ";
    int n;
    std::cin >> n;
    std::cout << "Difference equals " << foo->new_num(thing - n)->num() << std::endl;
    delete foo; // <-- the Foo object is deleted here
}

我知道可以在函数参数中使用运算符,但我只知道使用级别 2、4 到 15 和 17 的运算符以及赋值运算符 ? :、@ 987654324@ 和 --、一元 +-!~*&amp;sizeof 和演员表。像这样的东西:

foo((x < 3)? 5 : 6, --y * 7);
bar(player->weapon().decr_durability().charge(0.1), &shield_layers);

所以,我实际上有两个问题。

  1. new-as-an-argument 是好的做法吗?

  2. 如果new 有效,显然任何返回类型的运算符都有效,是否正在使用这些良好做法?

    ::new []throwsizeof...typeidnoexceptalignof

【问题讨论】:

  • (1) 通常没有 - 所有权不明确。 (2) 太笼统的问题。尽量避免在所有程序中使用new / delete,它们几乎总是可以替换为std::vector&lt;T&gt; 或拥有智能指针(通常为std::unique_ptr)或只是范围块中的临时对象{ .... }
  • 我见过其他人使用它,它看起来很聪明, -- 显然你正在查看 Java 或 C# 程序员编写的代码(以一种糟糕的方式) 来编写 C++ 代码。使用new 创建对象是这种情况发生的迹象。
  • 在这种情况下,您应该只传递 foo 的值,因为创建它并在之后立即删除它没有任何好处。查看When should I use the new keyword in C++?
  • Foo::new_num 返回的是什么?
  • 这是一种不好的做法——一方面,像foo(new Bar, new Baz); 这样的函数调用几乎不可能使异常安全(例如,如果先执行new Bar,然后再执行Baz 构造函数抛出一个异常,因此 foo() 永远不会被调用,你已经泄漏了一个 Bar 对象,因为没有人持有指向 Bar 的指针,可以 delete 它)

标签: c++ function memory dynamic-memory-allocation new-operator


【解决方案1】:

不,这根本不聪明。它采用了一个更简单、更通用的函数,并无缘无故地降低了它的功能,同时为你的程序创建了一个入口点,以解决难以调试的错误。

我不清楚 Foo::new_num 的确切用途(现在它无法编译),所以我不会直接针对您的示例,但请考虑以下两个代码示例:

void bad_function(int i, F * f)
{
  f->doSomething(i);
  delete f;
}

// ...

bad_function(0, new F(1, 2, 3));

void good_function(int i, F & f)
{
  f.doSomething(i);
}

// ...

good_function(0, F(1, 2, 3));

在这两种情况下,您都分配了一个新的 F 对象作为方法调用的一部分,并且在您使用完毕后将其销毁,因此使用 bad_function 而不是 good function 并没有任何优势。但是,您可以用 good_function 做很多事情,而用 bad_function 做这些事情并不容易,例如

void multi_function(const std::vector<int> & v, F & f)
{
  for(int i : v) { good_function(i, f); }
}

使用good_function 版本意味着语言本身也会阻止你做各种你不想做的事情,例如

F * f;  // never initialized
bad_function(0, f); // undefined behavior, resulting in a segfault if you're lucky

这也是更好的软件工程,因为它使人们更容易从其签名中猜测您的函数的作用。如果我调用一个函数,其目的涉及从控制台读取一个数字并进行算术运算,我绝对不希望它删除我传入的参数,并且在我花了半个小时找出导致一些不相关的一些模糊崩溃的原因之后我要对编写该函数的人愤怒的部分代码。


顺便说一句,假设F::doSomething不会以任何方式改变F的当前实例的值,它应该被声明为const

class F
{
  void doSomething(int i) const;
  // ...
};

good_function 也应该采用const 参数:

void good_function(int i, const F & f);

这让任何查看签名的人都可以自信地推断该函数不会做任何愚蠢的事情,比如弄乱传递给函数的f 的值,因为编译器会阻止它。这反过来又让他们更快地编写代码,因为这意味着少了一件需要担心的事情。

事实上,如果我看到一个带有 bad_function 之类签名的函数并且没有明显的原因,那么我会立即担心它会做一些我不知道的事情'不想要,我可能会在使用它之前阅读该功能。

【讨论】:

  • 最后一点,可能还值得注意的是F f; bad_function(0, &amp;f); 也一样糟糕。
  • @dxiv:是的,这绝对是一个非详尽的bad_function 不好的原因列表。
  • 如果我想在使用对象时修改它怎么办?
  • @PatrickO'Brien:然后传递非const 引用。
猜你喜欢
  • 1970-01-01
  • 2016-11-10
  • 1970-01-01
  • 2011-12-19
  • 1970-01-01
  • 2020-05-19
  • 1970-01-01
  • 1970-01-01
  • 2011-12-25
相关资源
最近更新 更多