【问题标题】:Coding style when using the stack for memory management in C/C++在 C/C++ 中使用堆栈进行内存管理时的编码风格
【发布时间】:2011-08-30 14:48:32
【问题描述】:

来自 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);

第二个有,但不漂亮:

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

在我弄乱我的代码之前,经验丰富的 C/C++ 程序员如何看待这种编码风格?

【问题讨论】:

  • 如果您要一次增加一个,为什么不改用operator++
  • @Rafe:哪段代码是“错误的”?我只问,因为both areperfectly fine
  • 我认为他的同事指的是 RAII 风格的 C++,而不是通过 ref 传递 POD 类型。基于堆栈的 POD 类型不需要“内存管理”。
  • 我不知道为什么你的同事关于内存分配的建议会导致你以这种方式写inc。我认为他的意思是如果你必须使用new(和delete),你应该只在构造函数中使用new,并将相应的delete放在析构函数中。这将避免大多数内存分配问题(内存泄漏、双重释放、悬空指针等)
  • 为什么是 C 标签? C 和 C++ 不是同一种语言。特别是,C 没有您所指的“通过引用”机制。所以你的问题对C来说真的毫无意义。

标签: c++ c memory stack


【解决方案1】:

我强烈反对

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

【讨论】:

【解决方案2】:

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

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

int y = inc(inc(2));

【讨论】:

    【解决方案3】:

    这不太可能是您同事所指的编程风格。 POD 类型(如整数或简单结构)不是您通常关心的数据。 Resource Acquisition is Initialization 或 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
    }
    

    【讨论】:

      【解决方案4】:

      对于原始类型,这是可以的:

      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 的情况下编写这种函数,除非你知道它何时失败。

      【讨论】:

        【解决方案5】:

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

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

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

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

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

        【讨论】:

          【解决方案6】:

          我认为在您的第二个示例中,您不想引用 y,所以不是

          int inc(int x, int &y)
          

          你应该有

          int inc(int x, int y)
          

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-10-10
            • 1970-01-01
            • 2011-09-16
            • 2010-11-13
            • 1970-01-01
            • 2011-09-24
            • 1970-01-01
            • 2012-12-24
            相关资源
            最近更新 更多