8

I've got a class A, which consists of objects B and C. How to write a constructor of A that gets B and C objects? Should I pass them by value, by (const) reference, or a pointer? Where should I deallocate them?

I thought about pointers, because then I could write:

A a(new B(1,2,3,4,5), new C('x','y','z'))

But I don't know whether it's a good practice or not. Any suggestions?

mik01aj
  • 11,928
  • 15
  • 76
  • 119
  • 1
    See [_How to pass objects to functions in C++?_](http://stackoverflow.com/questions/2139224/how-to-pass-objects-to-functions-in-c/2139254#2139254) – sbi Jul 08 '10 at 08:58

4 Answers4

10

Usually you pass by const reference:

A a(B(1,2,3,4,5), C('x','y','z'))

No need for pointers here.

Usually you store values unless copying is too inefficient. The class definition then reads:

class A {
private:
    B b;
    C c;
public:
    A(const B& b, const C& c): b(b), c(c) { }
};
Philipp
  • 48,066
  • 12
  • 84
  • 109
4

Should I pass them by value, by (const) reference, or a pointer?

  1. By const reference if object is big
  2. by value if object is small
  3. by const pointer if it is an optional argument that can be zero (i.e. "NULL")
  4. by pointer if it is an optional argument that can be zero but will be owned (i.e. deallocated) by constructed class.

Please note that if your class have internal instances of B and C, then passing them by reference, value or const reference, will most likely involve using copy constructor or assignment operator. Which won't be necessary with pointers.

A a(new B(1,2,3,4,5), new C('x','y','z'))

Normally(i.e. not always) it is a bad idea, because:

  1. if A doesn't deallocate arguments, you have a memory leak.
  2. If A takes ownership of arguments and deallocates them, then you won't be able to pass values allocated on stack as arguments. Still, depending on your code design this may be acceptable (Qt 4 frequently takes ownership of objects created with new)

Where should I deallocate them?

The best idea is to make sure that compiler deallocates arguments automatically for you. This means passing by reference, const reference or by value. Or using smart pointers.

SigTerm
  • 26,089
  • 6
  • 66
  • 115
  • There are alternatives to passing a raw pointer. E.g. `Boost.Optional` for case 3, and a smart pointer like `boost::shared_ptr` for case 4. Then you don't have to deal with manual deallocation. – Philipp Jul 08 '10 at 08:26
  • 2
    And I think that is important to say that if A constructor throws an exception you have a memory leak because nobody deallocate B and C. Another reason to use smart pointers :) – Ricardo Muñoz Jul 08 '10 at 08:43
  • 1
    @Philipp: can you have an optional reference? I would use `shared_ptr` for case 3 and `auto_ptr` for case 4 (unless it's actually an array, in which case other smart pointers are applicable). – Ben Voigt Jul 08 '10 at 08:57
  • @Ben Voigt: I wouldn't use `auto_ptr` unless transfer of ownership semantics (which users often find surprising) is required. It can be confusing if passing pointers to a function “steals” them. Regarding the optional parameters, the Boost.Optional manual says that optionals of references are possible, and you can always use references to optional objects, of course. But usually optional arguments are handled via overloading in C++. – Philipp Jul 08 '10 at 09:03
  • I have a class that has 2 doubles, is that big or is it small? The methods in my application process millions of these objects. I have another class which has a couple of pointers. One of those is a pointer to block of data that may contain 100,000s of the previous class, or it may contain just one. The issue isn't the size of the class but the cost of copy construction. Neither can always be determined at the time the class is designed. Thus it is always best to pass by reference UNLESS one CANT. – lilburne Jul 08 '10 at 09:41
  • @lilburne: "Thus it is always best to pass by reference UNLESS one CANT" Nope, and you should never say "always". If you pass char by reference, it will take more bytes/operations. Passing smart pointer by reference also doesn't make much sense. "Neither can always be determined at the time the class is designed." Use profilers. – SigTerm Jul 08 '10 at 11:35
  • @Phillip: "There are alternatives to passing a raw pointer." And those alternatives may lead to major screwup in certain scenarios. For example, if the data can't be deallocated normally, and if some routine "steals" the pointer. And again, smart pointers typically deal with memory that was allocated by *new*, not an optional object on stack. It isn't always necessary to use smart pointer. Smart pointers are required only if it should be possible to share ownership to object. – SigTerm Jul 08 '10 at 11:37
  • @SigTerm: there are also smart pointers without shared semantics (and without overhead), like `unique_ptr`. I agree that situations where passing raw pointers is useful or necessary do exist, but you must be extra careful when doing so, and using a smart pointer or reference is much simpler. – Philipp Jul 08 '10 at 14:39
  • @liburne: I'd say that passing "small" objects by const reference doesn't introduce a performance penalty on new compilers. But of course SigTerm is right: profile, profile, profile, don't speculate. – Philipp Jul 08 '10 at 14:40
  • @SigTerm: I'll profile when there is a problem. I can see no reason to have my team profiling as a matter of course, they can do that when our timing tests show a slow down. As our application is at least twice as fast as our nearest competitor, we must be doing something right. Mostly speed increases come about by a redesign of algorithms not by micro tweaks. That said when copying becomes measurable then it is a major issue, so why go down that road in the first place? – lilburne Jul 29 '10 at 10:19
  • @lilburne: "I can see no reason to have my team profiling..." Although this discussion is few weeks old... "As our application is at least twice as fast as our nearest competitor, we must be doing something right.". It means that your competitor is doing something *differently*, and doesn't mean anything else. Also by profiling you may discover to find a way to make your product 3x faster instead of 2x. It doesn't take much time, so what is a problem with profiling? – SigTerm Jul 29 '10 at 12:25
  • @SigTeam: Well actually I think our competitors are trying to play catchup. But no matter. The problem with willy-nilly profiling is that you have to ensure that people aren't wasting their time tweaking away fractions of a second and making the code less readable in the process. You profile when there is a problem and more often than not it isn't micro optimisations that speed things up but algorithmic redesigns. – lilburne Jul 29 '10 at 12:45
  • @SigTeam: Whilst the profiler may point you at the area that is consuming time you usually need to step backwards to look at the larger structures and data flows. Making something twice does not give you as much bang as calling it 100 times less often will. – lilburne Jul 29 '10 at 12:47
  • @lilburne: I don't understand your point. I use AQTime profiler. For each run it returns a list of routines and total time spent in each routine - with or without children. Which can be compared with results from previous runs. How exactly you reduce time spent in particular routine or line is not a profiler's problem. New algorithm, or low-level optimization - it is your decision. Profiling returns numbers. How you will optimize program based on numbers - is not a profiler's problem. – SigTerm Jul 29 '10 at 13:11
  • @lilburne: "Whilst the profiler may point you at the area that is consuming time" This is incorrect. Profiler __will__ point you at the area that consumes the most time (there is no uncertainty). What you'll do after that is not a profiler's problem. Reduce number of calls, change algorithm, do low-level optimization - it is your choice and your decision to make. – SigTerm Jul 29 '10 at 13:11
  • @SigTeam: My profiler tells me that main() takes the most time >:) The last profiling session I undertook was because we had a 5% slowdown of our test runs. It turned out that it was because 5 independent routines were taking 1% of the time where in the past they had taken zip time. These were around the 200th or so in the list of functions that the profiler thought were important. The absolute time being buried amongst a bunch of other methods. They had added a little bit extra to the string handling, the memory allocation deallocation etc, and were well buried in amongst the noise. – lilburne Jul 29 '10 at 13:49
  • @SigTeam: Actually fixing that issue makes the program run slower because the fix was to use a form of indirection, however in the normal run of things you'd not notice as its microseconds, the issue was simply that our testing system reverts the state after each test. – lilburne Jul 29 '10 at 13:52
  • @SigTeam: Additionally I want people writing code in the clearest most simplest way possible. I don't want them warping code and designs to shave a percent here or a percent there unless we have a pressing reason to do so. In the example above the increase was 20 minutes over a several hours of testing a creep that we'd rather not have. If it had simply been a 5% bump in a single algorithm we'd not have bothered. As fixing the issue made the code far more complicated. – lilburne Jul 29 '10 at 14:00
  • @lilburne: "main() takes the most time >:)" Your profiler is correct, because main() always takes most of execution time *with children*. "The absolute time being buried" IMO, you need a different profiler and a bit more experience with optimizing things up. The situation you described looks like programmer's fault. It is up to you to locate function with max time with/without children(except main) or max calls or with most execution time among children, and decide whether to optimize or not. If you have troubles and can't analyze profiling data and make proper decision - it is your fault. – SigTerm Jul 29 '10 at 14:10
  • @lilburne: "In the example above the increase was 20 minutes over a several hours of testing a creep that we'd rather not have." Look, when you do something, you lose and gain something. Lose simplicity, or gain speed. Increase speed or reduce memory requirements. Deciding what is more important up to you and is outside of scope of this discussion. It is unclear what exactly you're trying to prove, so I'm not interested in continuing this discussion. If you have questions - ask them using "ask question" button. – SigTerm Jul 29 '10 at 14:15
  • @SigTeam: Yep that will be right your profiler is the most awesomest EVAR, and all we do is write bugs. That we got tired of finding performance issues due to copy-ctors being invoked in function calls and now make sure that classes get passed by const ref couldn't possibly have any bearing on a suggestion that one shouldn't pass classes by value. Nope instead we get an irrelevant bit of tosh about chars. – lilburne Jul 30 '10 at 12:43
2

What you pass in depends on your needs.

Do you need a copy of the thing you are passing in? Then pass by const-reference.

struct A
{
    A(const B& b, const C& c) : m_b(b), m_c(c) {}

private:
    B m_b;
    C m_c;
};

And construct it like this:

A myA(B(1,2,3), C(4,5,6));

If you want your A object to refer to some other B and C objects (but not own them) then use pointers (or possibly references).

Peter Alexander
  • 53,344
  • 14
  • 119
  • 168
  • I want it to own them, so this is not the case. Thank you anyway :) – mik01aj Jul 08 '10 at 08:03
  • 1
    Do you have a need for dynamically allocated objects? Else **this** is your case. The code sample provides the simplest most natural solution in C++ when dealing with composition. – David Rodríguez - dribeas Jul 08 '10 at 08:08
  • I was referring to the last sentence. I'm going to use const references, as most of people here suggested. – mik01aj Jul 08 '10 at 08:12
  • Interestingly, you and utnapistim propose different solution for the case that `A` owns the objects: you propose values, utnapistim proposes pointers. – Philipp Jul 08 '10 at 08:35
1

Edit: The examples given here do not respect the rule of the Big Three (thanks @Philipp!). If the definition of A is used as given below, the code will crash on copy construction for A, or on assignment for A. To define the code correctly, the assignment operator and copy constructor should be explicitly defined for A (or explicitly forbidden - declared as private and never implemented). (end Edit)

Should I pass them by value, by (const) reference, or a pointer?

If A uses B and C, then hold them by reference or pointer inside of A. To choose between reference and pointer, see how B and C are allocated.

If they are local stack objects constructed in the same scope as A, then pass them by const reference.

If they are dynamically allocated objects that A uses, make A own them: pass them by pointers, and have A's destructor delete them.

If they are optional components of A, pass them by pointer (that can be null).

If A is not responsible of deleting them, pass them by * const.

Where should I deallocate them?

Usually where you no longer need them :).

If they are needed past the scope of A (if they are external objects that A uses) then delete them when A's scope is complete.

If they are owned by A, delete them in the destructor for A. It may make sense to also delete them during the lifetime of A, if the pointers should be changed.

Here's an example, where B is a replaceable component injected into A (and owned by A) and C is an optional component owned by A (but injected into A also).

("owned by" means A is responsible for deleting both objects)

class B;
class C;

class A
{
    B* b;
    C* c;
public:
    A(B* const bb, C* const cc = 0) // cc is optional
    : b(bb), c(cc)
    {
    }

    void resetB(B* const bb = 0)
    {
        delete b;
        b = bb;
    }

    ~A()
    {
        resetB();
        delete c;
    }
};

{
    A a(new B, new C);
    a.resetB(); // delete B
    a.resetB(new B); // delete former B and set a new one
} // both members of A are deleted

But I don't know whether it's a good practice or not. Any suggestions?

It's up to you really, but you can write A a(B(1, 2, 4), C(1, 2, 3)) as easy as A a(new B(1, 2, 4), new C(1,2,3)); (in the former case - the one without new - the A::b and A::c should be references or objects/values inside the class, and A should not delete them at all).

The question should not be if you want to write the statement with dynamic allocation for B and C but if you need to. Dynamic allocation is slow and if you don't have a requirement for it you shouldn't do it.

utnapistim
  • 26,809
  • 3
  • 46
  • 82
  • 1
    Your example doesn't stick to the Rule of the Big Three. Consider using a smart pointer instead of the raw pointer. – Philipp Jul 08 '10 at 08:39
  • @Philipp - Thanks, I should have specified that. My example is not an example of complete code, just of how to pass the parameters around. I will add a comment to my answer. – utnapistim Jul 08 '10 at 08:47