这是动态内存分配的好习惯还是坏习惯?
Is this good or bad practice with dynamic memory allocation?
我看到其他人使用过它,看起来真的很聪明,但我不确定这是好事还是坏事。它有效,而且我个人喜欢它的工作方式,但是在更大的程序范围内这样做真的有用吗?
他们所做的是在实际函数参数中动态分配一些数据类型,并在函数中删除它。这是一个例子:
#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 上的运算符以及赋值运算符 ? :
、++
和 --
、一元 +
和 -
、!
、~
、*
和 &
, sizeof
和演员表。像这样的东西:
foo((x < 3)? 5 : 6, --y * 7);
bar(player->weapon().decr_durability().charge(0.1), &shield_layers);
所以,其实我有两个问题。
new
-as-an-argument 是好的做法吗?
如果 new
有效,显然任何返回类型的运算符都有效,是否正在使用这些良好做法?
::
、new []
、throw
、sizeof...
、typeid
、noexcept
、alignof
不,这一点都不聪明。它采用了一个可以更简单、更通用的函数,并且无缘无故地降低了它的功能,同时为难以调试的错误创建了一个进入你的程序的入口点。
我不清楚 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
这样的签名的函数并且没有明显的原因,那么我会 立即 担心它会做一些我不想做的事情,我可能会在使用它之前阅读它的功能。
我看到其他人使用过它,看起来真的很聪明,但我不确定这是好事还是坏事。它有效,而且我个人喜欢它的工作方式,但是在更大的程序范围内这样做真的有用吗?
他们所做的是在实际函数参数中动态分配一些数据类型,并在函数中删除它。这是一个例子:
#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 上的运算符以及赋值运算符 ? :
、++
和 --
、一元 +
和 -
、!
、~
、*
和 &
, sizeof
和演员表。像这样的东西:
foo((x < 3)? 5 : 6, --y * 7);
bar(player->weapon().decr_durability().charge(0.1), &shield_layers);
所以,其实我有两个问题。
new
-as-an-argument 是好的做法吗?如果
new
有效,显然任何返回类型的运算符都有效,是否正在使用这些良好做法?::
、new []
、throw
、sizeof...
、typeid
、noexcept
、alignof
不,这一点都不聪明。它采用了一个可以更简单、更通用的函数,并且无缘无故地降低了它的功能,同时为难以调试的错误创建了一个进入你的程序的入口点。
我不清楚 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
这样的签名的函数并且没有明显的原因,那么我会 立即 担心它会做一些我不想做的事情,我可能会在使用它之前阅读它的功能。