1

I'm migrating a project to c++ because I hit a performance ceiling while developing it in c#. It's my first time using c++, however, and I'm finding myself doing something a lot that doesn't seem quite right...

Consider the following abstracted example:

class ClassC
{
    ClassC::ClassC(int option)
    {
        //do something
    }
}

class ClassB
{
    ClassC* objC

    ClassB::ClassB(ClassC* objC)
    {
        this->objC = new ClassC(*objC);
    }
}

class ClassA
{
    void functionA(void)
    {
        ClassB objB (&ClassC(2));
    }
}

ClassA has a function which creates a ClassB. ClassB's constructor accepts a ClassC, objC. objC is passed by reference because ClassC is not a primitive type, but its stored by reference because ClassC has no default constructor. However, because objC is created in static memory and will be destructed when functionA completes, ClassB needs to copy to the value pointed by objC into dynamic memory, then store a pointer to that copy.

This seems very round-a-bout to me, and makes me feel like im approaching something incorrectly. Is this a standard thing to do in c++?

EDIT: Everyone seems to be saying that the line ClassB objB (&ClassC(2)); is incorrect because the value of the ClassC object will be lost before ClassB can make a copy of it. But I have compiled my example and this is not the case. Here is revised, working code:

class ClassC
{
    int option;

public:
    ClassC::ClassC(int option)
    {
        this->option = option;
    }

    int ClassC::getOption(void)
    {
        return option;
    }
};

class ClassB
{
    ClassC* objC;

public:
    ClassB::ClassB(ClassC* objC)
    {
        this->objC = new ClassC(*objC);
    }

    int ClassB::getOption(void)
    {
        return objC->getOption();
    }
};

class ClassA
{
public:
    static ClassB functionA(void)
    {
        return ClassB (&ClassC(2));
    }
};

int main(void)
{
    ClassB objB = ClassA::functionA();

    int test = objB.getOption(); //test = 2, therefore objC was copied successfully.

    return 0;
}
billz
  • 44,644
  • 9
  • 83
  • 100
Madison Brown
  • 331
  • 3
  • 10
  • Are you getting any errors? What are they? – David G Oct 06 '13 at 03:59
  • 1
    `ClassB objB (&ClassC(2));` this here is taking the address of a temporary. Not such a good thing. – goji Oct 06 '13 at 04:00
  • You need to learn about object lifetime and ownership semantics. These things you have to worry about far less in `c#`. – goji Oct 06 '13 at 04:02
  • 1
    1. don't use pointer if it's avoidable. 2. pass by const ref instead of pointer – billz Oct 06 '13 at 04:02
  • Other than vague suggestions it's hard to suggest what to do with the example code as your intentions aren't really clear. – goji Oct 06 '13 at 04:04
  • There too much illegal syntax in your example to make sense of the question. It is illegal to use qualified names to declare constructors in class definition, like `ClassC::ClassC`. It is illegal to apply `&` to temporaries, as in `&ClassC(2)`. None of this will compile. Also, there isn't a single reference in your code, yet the question talks about references. What references are you talking about? – AnT stands with Russia Oct 06 '13 at 04:45
  • No, i'm not getting any errors and the program runs as expected. This is just a brief example, not my actual code. @AndreyT sorry, normally the classes would be split into header and cpp files, so i was a little confused as to how they should be represented in one chunk. As for the "references", i was referring to the general programming concept of passing by reference. i understand that in c++ there is a distinction between pointers and references, so sorry for the confusion. – Madison Brown Oct 06 '13 at 16:37
  • @AndreyT: " None of this will compile." You shouldn't say thing like that - such statement implies that you know every quirk of every compiler. This code compiles just fine on Microsoft Compiler fro VS2008 express. – SigTerm Oct 06 '13 at 17:05

4 Answers4

4

Not sure what's your real question, but your code seems fragile. I'd like to rewrite your code as you have shown to this way:

class ClassC
{
    explicit ClassC(int option)
//  ^^^^^^^^ stop implicit conversion, if constructor takes one parameter
    {
        //do something
    }
};

class ClassB
{
    ClassC objC;                         // store by value instead of pointer. 
                                         // Even smart pointer will be better option than raw pointer

    explicit ClassB(const ClassC& objC)  // pass by const reference instead
    : objC(objC)                         // use member initializer list to initialize members
    {
    }
};

class ClassA
{
    void functionA(void)
    {
        ClassB objB(ClassC(2));
    }
};
billz
  • 44,644
  • 9
  • 83
  • 100
  • 1
    In this code, you are creating a temporary ClassC, then passing a reference to a temporary, then copying it again into objC. I would change the constructor to `explicit ClassB(ClassC c) : objC(std::move(c)) {}`, which would result in no copies since the temporary will be moved into the classB constructor parameter (since its an rvalue), and will then used to move construct objC. – Muhammad Faizan Oct 06 '13 at 04:26
  • @MuhammadFaizan I see your point. however, there isn't much value to add move semantics to OP atm. – billz Oct 06 '13 at 04:32
  • @MuhammadFaizan The temporary will still be in scope, and he isn't storing the reference, so there is nothing wrong with using it that way. Additionally, `ClassB` will invoke `ClassC`'s copy constructor, again avoiding the reference to a temporary problem. The move semantics would only help to improve performance (avoiding making all those copies). – Zac Howland Oct 06 '13 at 04:33
  • Given what I think the OP is looking for, I up-voted this as well (and think it should be the accepted answer). I concur that move-semantics are the ideal way to go as well. The answer I posted was to cover the possibility that the OP was looking for shared-semantics, but I think this is likely more inline with what they're going for. – WhozCraig Oct 06 '13 at 04:46
  • As a side note, this answer is basically the same thing the OP had posted (just without dynamic memory): Passing a reference of a temporary, which is okay because it is being copied (in the OP, via dynamic memory allocation, here by static memory). – Zac Howland Oct 06 '13 at 04:56
  • @billz i would certainly prefer to store objC by value. However, the problem is that when i try to declare objC as `ClassC objC;`, i get the error, "No default constructor exists for class ClassC". This is why I have been storing it as a pointer. – Madison Brown Oct 06 '13 at 16:58
2

Taking the address of a temporary and saving it off for later use is a big no-no.

ClassB objB (&ClassC(2));  // taking address of temporary

Further, even passing a const-reference of a temporary across a function parameter list will not extend the lifetime further than the function invoke. I.e. once the constructor is done firing the reference is toast, so this:

class ClassB
{
    const ClassC& objC;

public:
    ClassB(const ClassC& objC) : objC(objC)
    {
    }
};

won't work either. More info can be read here about the details for why.

It would work if you did this:

ClassC objC;
ClassB objB(objC);

but then again, so would your original sample.

One way to have the lifetime of an external object guaranteed is to dynamically allocate the object through smart-pointer ownership. Consider this:

class ClassB
{
    std::shared_ptr<ClassC> ptrC;

public:

    ClassB(std::shared_ptr<ClassC> ptrC)
        : ptrC(ptrC)
    {
        // access the instance with ptrC->member()
    }
};

Now you can do this:

ClassB objB(std::make_shared<ClassC>(2));

and even if objB is value-copied (like in a sort-operation on a container, etc) the shared instance is still intact. The last man out the door turns off the lights (in this case, deletes the shared ClassC object).

Obviously its rather pointless for a single instance that will only be held by a single parent. In that case, I totally concur with other answers that strongly suggest you use move-semantics. If, however, you have a true need for a shared resource this is one way to consider doing it.


EDIT Adding pass-through constructor to ClassB as a trivial example.

I just realized everyone was so harped on assisting you in constructing your ClassC object, that maybe all you need is a way to provide parameters to objC for construction. I.e. Perhaps you fully intend on objB to outright own its own private instance of objC and all you need is a way to get parameters to it for initialization.

This is what a constructor initializer list is made for. See the code below, which (based on your comments, will probably work for you and is much simpler to understand.

class ClassB
{
    ClassC objC;

public:
    // default constructor. initializes objC with default value
    ClassB() : objC(0)
    {
    }

    // explicit pass-through of params to `objC` construction
    explicit ClassB(int option) : objC(option)
    {
    }
};

This makes your code in ClassA simply this:

ClassB objB(2);

This will invoke ClassB::ClassB(int), passing the provided parameter to construction of the internal objC object instance of type ClassC.

Community
  • 1
  • 1
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • Random observation: I'm just learning C++ for the first time, and it's amazing that the best solutions to something that requires pretty little thought in C# or Java are either move semantics or shared pointers, both of which are C++11 concepts. I wonder how everyone survived before the new standard :) – Muhammad Faizan Oct 06 '13 at 04:51
  • 1
    @MuhammadFaizan With careful coding. Move semantics primarily provide an optimization (as before it would involve making copies of objects). Smart pointers have been around for quite a while, though. Prior to those templates, it was a lot of debugging and making sure you properly managed your memory. – Zac Howland Oct 06 '13 at 04:54
  • 2
    @MuhammadFaizan I came from C, and C++ since Bjarne first invented the thing, and without-doubt C++11 was a *long* time relief for many of the problems we experienced in C++'s-past. Perfect forwarding seriously changed the world, for example. – WhozCraig Oct 06 '13 at 04:54
  • @WhozCraig "once the constructor is done firing the reference is toast" see my edit... i have tried compiling it and this is not the case. the way i'm currently doing it does actually produce the expected result, semantically incorrect as it may be. – Madison Brown Oct 06 '13 at 17:03
  • @WhozCraig "The last man out the door turns off the lights" I have to admit I dont fully understand smart pointers (as i'm sure you can tell), but from your description, it seems that this is not necessarily what I am looking for. In my actual program, it really should be ClassB's responsibility to delete objC: that's why ClassA creates it in static memory, then ClassB copies it to dynamic memory and takes responsibility for deleting it. Any thoughts on this? – Madison Brown Oct 06 '13 at 17:06
  • @MadisonBrown in your code, ClassA doesn't create the ClassC instance in static memory. its in temporary storage. If ClassB is responsible exclusively for the content of such a creation, then either *it* needs to create it (passing the ClassC-construct params to an overloaded ClassB constructor that creates `objC` in the initializer list, I can show you this if you want), or you can use move-construction. The answer above will work, but is honestly overkill for what you're needs are. It is designed to support what you apparently want as well as *multiple* B's share the same C. – WhozCraig Oct 06 '13 at 17:32
  • @WhozCraig one example of how this situation is arising for me is in the factory paradigm. So think of ClassA like a factory: it creates a whole bunch of objects, weaves them together correctly, but then steps out of the way. Therefore all the objects it creates need to be responsible for their own deleting. Is this the wrong approach? – Madison Brown Oct 06 '13 at 17:48
  • However, I think maybe my problem is a bit more fundamental. I have not been creating default constructors in my classes where there is no logical default construction. The consequence of this is that I have not been able to initialize class variables by value. It seemed wrong to me for the class to create a default object just to have that object overwritten with the real object in the constructor. this seems like an unnecessary step to me. But is that just how it's done? – Madison Brown Oct 06 '13 at 17:50
  • @MadisonBrown Not necessarily. see the update to this answer. In all the madness that edit may well be what you *really* need. – WhozCraig Oct 06 '13 at 17:52
  • Ok, this is definitely much closer to what I'm looking for, and would certainly be fine in this example. The problem then is that in my real program, ClassB would have to accept around 30-40 arguments in order to construct 5-10 different class objects, and that seems like it would just be a huge mess. So I guess the only reason I was having ClassA pass pre-made objects instead of the arguments is to make things more readable. – Madison Brown Oct 06 '13 at 18:00
  • @MadisonBrown Ah.. I see. With that much configuration info it sounds like you need something similar to a "traits" idiom. You're may not be familiar with how they work, but you likely use them every day. `std::string`, for example, is a template the provides a collection of data to the template-interface by way of a traits type. You might be able to use something similar. Regardless, if nothing else this question has tossed up some ideas for you, so I'm glad you got something out of it. – WhozCraig Oct 06 '13 at 18:04
1

Your constructor for ClassC is irrelevant as what will be called is the copy-constructor

class ClassC
{
    ClassC(int option) // defines a constructor that takes an int
    {
        //do something
    }
}

class ClassB
{
    ClassC* objC

    ClassB(ClassC* objC)
    {
        this->objC = new ClassC(*objC); // dereferences objC calling ClassC::ClassC(const Class& obj) - the default copy constructor.
    }
}

class ClassA
{
    void functionA(void)
    {
        ClassB objB (&ClassC(2)); // passing a reference to a temporary ... bad idea, but since it is copied in ClassB (the object, not the pointer), it will appear okay - if your compiler lets this compile (newer ones should/will likely throw an error "cannot take address of rvalue temporary")
    }
}

All in all, this code would be better off with many of the suggestions already mentioned, but it was worth noting that the copy-constructor for ClassC is what was called in ClassB.

Zac Howland
  • 15,777
  • 1
  • 26
  • 42
0
ClassB objB (&ClassC(2));

This is a bad thing to do. You are getting the address of a temporary object, which will vanish after this line. As already stated, try to use references instead of pointers. Most of the time, you can replace a pointer with a reference, which is a very safe approach (it will never be "NULL").

You could write

class ClassB
{
    ClassC& objC;

    ClassB::ClassB(const ClassC& objC) :
        objC(objC)
    {
    }
}
kamshi
  • 605
  • 6
  • 19
  • 1
    How would we ensure that objC has a longer lifetime than the ClassB instance? – Muhammad Faizan Oct 06 '13 at 04:07
  • 2
    And how do we initialize a reference from a const-reference? – WhozCraig Oct 06 '13 at 04:08
  • I disagree with this answer. I think in most cases storing a reference is as bad as storing a pointer (or even worse, perhaps). Also, like @WhozCraig pointed out this code would not compile. –  Oct 06 '13 at 04:24
  • Yeah, sorry about that. I wanted to make the member objC not a reference, but a simple member of ClassB, since it seems to me he wants to make a copy of the argument objC. – kamshi Oct 06 '13 at 04:36