2

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?

Grav
  • 1,714
  • 14
  • 27
  • If you're gonna increment by one at a time, why don't you use the `operator++` instead? – Tony The Lion May 22 '11 at 14:28
  • @Rafe: Which piece of code is "wrong"? I only ask, because [both are](http://codepad.org/YS0FVZGw) [perfectly fine](http://codepad.org/GvMyNc2D). – Lightness Races in Orbit May 22 '11 at 14:31
  • I think his colleague was referring to RAII style C++ rather than passing POD types by ref. No "memory management" is required for stack based POD types. – user7116 May 22 '11 at 14:31
  • 1
    I'm not sure why your colleagues advice about memory allocation leads you to be writing `inc` in this way. I think he means is that if you have to use `new` (and `delete`) you should use new only in constructors and put the corresponding delete in a destructor. That will avoid most of the problems with memory allocation (memory leaks, double frees, dangling pointers, etc.) – andrewdski May 22 '11 at 14:33
  • 1
    why the C tag? C and C++ are not the same languages. In particular, C doesn't have the "pass by reference" mechanism to which you are referring to. so your question is really pointless for C. – Jens Gustedt May 22 '11 at 15:28

6 Answers6

5

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
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
3

There is a third much simpler way:

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

int y = inc(inc(2));
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
1

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
}
user7116
  • 63,008
  • 17
  • 141
  • 172
1

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.

Łukasz Milewski
  • 1,897
  • 13
  • 14
0

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)
Constantinius
  • 34,183
  • 8
  • 77
  • 85
0

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.

Xymostech
  • 9,710
  • 3
  • 34
  • 44