在 C/C++ 中使用堆栈进行内存管理时的编码风格

发布于 2024-11-08 15:22:18 字数 490 浏览 0 评论 0原文

来自 Java 背景,我试图学习如何以最简单的方式处理 C/C++ 中的内存分配(取消)分配。

一位同事建议我只为成员变量分配内存,让堆栈处理局部变量。我不完全确定这个概念叫什么,但这意味着函数将像这样实现:

void inc(int x, int &y){
  y=x+1;
}

另一种方式是这样的:

int inc(int x, int &y){
  y=x+1;
  return y;
}

第一个禁止我在表达式中使用它,即:

int y;
inc(2,y); 
inc(y,y);

第二个可以,但它不是' t Pretty:

int y;
y=inc(inc(2,y),y);

在我搞乱我的代码之前,经验丰富的 C/C++ 程序员对这种编码风格有何看法?

Coming from a Java background, I'm trying to learn how to handle memory (de)allocation in C/C++ in the simplest way.

A colleague suggested that I only allocate memory for member variables and let the stack handle the local variables. I'm not entirely sure what this concept is called, but it means that functions would be implemented like this:

void inc(int x, int &y){
  y=x+1;
}

Another way would be this:

int inc(int x, int &y){
  y=x+1;
  return y;
}

First one prohibits me from using it in an expression, i.e:

int y;
inc(2,y); 
inc(y,y);

Second one does, but it isn't pretty:

int y;
y=inc(inc(2,y),y);

Before I go mess up my code, what do seasoned C/C++ programmers think about this coding style?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

微凉徒眸意 2024-11-15 15:22:18

对于使用此函数的程序员来说,我强烈劝阻他们

int inc(int x, int &y) {
   y=x+1;
   return y;
}

,不清楚为什么该函数会修改输入并返回值,并且它们都是同一个对象


实际上,在我看来,选择是:

// #1
void inc(int x, int& y) {
   y=x+1;
}

int y = 0;
inc(2, y);

// #2
int inc(int x) {
   return x+1;
}

int y = inc(2);

在一般情况下,我仍然更喜欢 #2 因为我发现“输出参数”过时且使用起来很笨拙。正如您所指出的,您最终会与表达式作斗争,并且调用函数1时实际发生的情况并不是很清楚。

再说一遍,如果您有一个比 int 更复杂的对象(例如,一个数组或一个大类,或者您只想“返回”多个对象),它可能会使对象所有权变得更容易如果您不在函数内创建任何新对象,则需要处理,使 #1 成为更方便的选择。

我认为我在这里试图得出的结论是,它取决于场景。试图对这些事情进行概括是愚蠢的行为。


1 - 使用指针而不是引用在一定程度上解决了这个问题,尽管它确实引入了膨胀,现在不得不费心检查无效指针:

// #3
void inc(int x, int* y) {
   assert(y); // at least, we can check that it's not NULL
   *y = x+1;
}

int y = 0;
inc(2, &y); // clear here that I'm passing a pointer

I would heavily discourage

int inc(int x, int &y) {
   y=x+1;
   return y;
}

To the programmer using this function, it's not clear why the function modifies an input, and returns the value, and they're both the same object.


Really, to my mind, the choice is between:

// #1
void inc(int x, int& y) {
   y=x+1;
}

int y = 0;
inc(2, y);

and

// #2
int inc(int x) {
   return x+1;
}

int y = inc(2);

In the general case, I still prefer #2 as I find "out parameters" archaic and clunky to use. As you point out, you end up struggling with expressions and it's not terribly clear what's actually going on when you invoke the function1.

Then again, if you have an object more complex than int (say, an array, or a large class, or you just want to "return" more than one object), it may make object ownership easier to deal with if you're not creating any new objects inside the function, making #1 the more convenient choice.

I think the conclusion I'm trying to draw here, is that it depends on the scenario. Trying to generalise about these things is a fool's errand.


1 - Using pointers rather than references solves that somewhat, though it does introduce bloat with now having to bother checking for invalid pointers:

// #3
void inc(int x, int* y) {
   assert(y); // at least, we can check that it's not NULL
   *y = x+1;
}

int y = 0;
inc(2, &y); // clear here that I'm passing a pointer
小嗷兮 2024-11-15 15:22:18

还有第三种更简单的方法:

int inc( int x ) {
   return x+1;
}

int y = inc(inc(2));

There is a third much simpler way:

int inc( int x ) {
   return x+1;
}

int y = inc(inc(2));
梦晓ヶ微光ヅ倾城 2024-11-15 15:22:18

这不太可能是您同事所指的编程风格。像整数或简单结构这样的 POD 类型不是您通常关心的数据。 资源获取即初始化,即 RAII,是 C++ 中的一种常见策略,它利用了堆栈分配的属性变量,从而保证在大多数情况下调用其析构函数。

假 RAII 代码:

// take a reference to some resource 'r'
void frob(resource& r, int val)
{
    other_resource or(val);

    or << r; // use of r requires no pointer manipulation, etc
} // 'or' is destructed at the end of 'frob'
  // even in exceptional situations.

int main (int argc, char argv[][])
{
    resource r(1, "a", 3.0);

    frob(r, 9);

    return 0; // after this 'r' will be destructed
}

This is not likely the style of programming your colleague was referring to. POD types like integers or simple structs are not the data you are usually concerned with. Resource Acquisition is Initialization, or RAII, is a common strategy in C++ which utilizes the property of stack allocated variables whereby their destructor is guaranteed to be called in most situations.

Faux-RAII code:

// take a reference to some resource 'r'
void frob(resource& r, int val)
{
    other_resource or(val);

    or << r; // use of r requires no pointer manipulation, etc
} // 'or' is destructed at the end of 'frob'
  // even in exceptional situations.

int main (int argc, char argv[][])
{
    resource r(1, "a", 3.0);

    frob(r, 9);

    return 0; // after this 'r' will be destructed
}
软糖 2024-11-15 15:22:18

对于基元类型,这是可以的:

int inc(int x) {
   return x+1;
}

对于更复杂的类型,这样做可以避免函数返回时进行额外的复制

void reverse_vector(const std::vector<int>& v, std::vector<int>* result) {
   if (!result) return;
   *result = v;
   std::reverse(result->begin(), result->end();
}
// ... 
std::vector<int> v;
std::vector<int> reversed;
reverse_vector(v, &reversed);

对于堆分配的对象,我建议使用 boost::shared_ptr (tr1::shared_ptr) 库。然后您就可以像在 java 中一样编写代码。

#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

class A {
public:
    A(int x, const std::string& str) 
      : x(x), str(str) {
    }

    void foo() {
    }
private:
    int x;
    const std::string& str;
};

// ...

boost::shared_ptr<A> a = boost::make_shared<A>(1, "hello");
a->foo();

您可以将 boost::shared_ptr 对象视为 java 引用。没有垃圾收集(只是引用计数),因此您必须自己关心周期。

请记住,shared_ptr 比标准指针慢一点。

另外,请务必记住,应避免复制大对象。 否则最好编写

void foo(const std::string& str);

而不是

void foo(std::string str);

除非您需要 foo 中的 str 副本,

编写。另一件事是编译器很聪明,会为你做一些优化。例如,reverse_vector 可以写为

std::vector<int> reverse_vector(std::vector<int> v) { // note copying!
   std::reverse(v.begin(), v.end());
   return v; // no additional copying of temporary due to RVO
}

此 RVO(返回值优化)非常有用,但有时编译器无法自动执行此操作。这就是为什么我建议在不依赖 RVO 的情况下编写此类函数,除非您在失败时学习。

For primitives types this is ok:

int inc(int x) {
   return x+1;
}

for more complex types do this to avoid additional copying when function returns

void reverse_vector(const std::vector<int>& v, std::vector<int>* result) {
   if (!result) return;
   *result = v;
   std::reverse(result->begin(), result->end();
}
// ... 
std::vector<int> v;
std::vector<int> reversed;
reverse_vector(v, &reversed);

For heap allocated object I suggest using boost::shared_ptr (tr1::shared_ptr) library. Then you can code almost the same as you would do in java.

#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

class A {
public:
    A(int x, const std::string& str) 
      : x(x), str(str) {
    }

    void foo() {
    }
private:
    int x;
    const std::string& str;
};

// ...

boost::shared_ptr<A> a = boost::make_shared<A>(1, "hello");
a->foo();

You can treat boost::shared_ptr objects as java references. There is no garbage collection (just reference counting) so you must care about cycles yourself.

Bear in mind that shared_ptr is a bit slower than standard pointer.

Also it is important to remember that you should avoid copying large objects. It is better to write

void foo(const std::string& str);

instead of

void foo(std::string str);

unless you need a copy of str in foo.

One more thing is that the compiler is smart and will do some optimizations for you. For example reverse_vector could be written as

std::vector<int> reverse_vector(std::vector<int> v) { // note copying!
   std::reverse(v.begin(), v.end());
   return v; // no additional copying of temporary due to RVO
}

This RVO (return value optimization) is very useful but sometimes the compiler fails to do it automatically. That's why I'd suggest to write this kind of functions without relying on RVO unless you learn when it fails.

孤千羽 2024-11-15 15:22:18

您的问题与内存分配无关,只是关于按值或按引用传递参数。

该函数将 y 参数作为引用传递,x 参数被复制。

void inc(int x, int &y){
  y=x+1;
}

我个人认为这种形式可能是令人严重头痛的根源,因为语法并没有偏离值传递。你应该避免它,除非你正在处理物体。我建议这种形式:

int inc(int x){
  return x+1;
}

y=inc(2)

Your question has nothing to do with memory allocation, just about passing parameters by value or by reference.

This function passes th y parameter as reference, the x parameter is being copied.

void inc(int x, int &y){
  y=x+1;
}

I personally think that this form might the source of serious headache, since the syntax does not divert from passing by value. You should avoid it, unless you are dealing with objects. I suggest this form:

int inc(int x){
  return x+1;
}

y=inc(2)
拿命拼未来 2024-11-15 15:22:18

我认为在您的第二个示例中,您不希望引用 y ,因此

int inc(int x, int &y)

您应该引用 y

int inc(int x, int y)

,因为当您在函数内编辑 y 时,它会再次编辑原始内容y 而不仅仅是本地副本,这不是您想要的。

I think that in your second example you don't want a reference to y, so instead of

int inc(int x, int &y)

you should have

int inc(int x, int y)

because when you edit y inside your function it again edits the original y and not just a local copy, which isn't what you're going for.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文