views:

142

answers:

4

Using C++ I built a Class that has many setter functions, as well as various functions that may be called in a row during runtime. So I end up with code that looks like:

A* a = new A();
a->setA();
a->setB();
a->setC();
...
a->doA();
a->doB();

Not, that this is bad, but I don't like typing "a->" over and over again.
So I rewrote my class definitions to look like:

class A{
public:
    A();
    virtual ~A();

    A* setA();
    A* setB();
    A* setC();
    A* doA();
    A* doB();

    // other functions

private:

    // vars
};

So then I could init my class like: (method 1)

A* a = new A();
a->setA()->setB()->setC();
...
a->doA()->doB();

(which I prefer as it is easier to write)
To give a more precise implementation of this you can see my SDL Sprite C++ Class I wrote at http://ken-soft.com/?p=234

Everything seems to work just fine. However, I would be interested in any feedback to this approach. I have noticed One problem. If i init My class like: (method 2)

A a = A();
a.setA()->setB()->setC();
...
a.doA()->doB();

Then I have various memory issues and sometimes things don't work as they should (You can see this by changing how i init all Sprite objects in main.cpp of my Sprite Demo).
Is that normal? Or should the behavior be the same?
Edit the setters are primarily to make my life easier in initialization. My main question is way method 1 and method 2 behave different for me?

Edit: Here's an example getter and setter:

Sprite* Sprite::setSpeed(int i) {
    speed = i;
    return this;
}

int Sprite::getSpeed() {
    return speed;
}
+4  A: 

What you have implemented there is called fluent interface. I have mostly encountered them in scripting languages, but there is no reason you can't use in C++.

soulmerge
Thanks, I couldn't think of the name for the initialization style.Do you by chance have any idea why A* a = new A() works fine but when I used A a = A(), then my initialization method seems to have some memory problems. :/
KennyCason
@KennyCason: It's nearly impossible to guess about what might go wrong with memory management unless you show us some of the relevant code.
Jerry Coffin
@Jerry there is a link in the question. And to be honest it may be tough to look through as there is a lot there. So that's why I edited my question to reflect "method 1" and "method 2", should they function the same? And if not, then why, and if they should, then I will know the problem lies deeper in the code.
KennyCason
@Jerry Specifically note in main.cpp I Initialize my Sprite objects like Sprite* sprite = new Sprite("file.bmp",frames,animSpeed);then sprite->setTransparency(255,0,255)->flipVertical()->otherstufF() That works fine, no apparent memory leaks, but if I switch all the sprite object so Sprite sprite = Sprite(...) and sprite.setTransparency(255,0,255)->flipVertical()->otherstufF() Then I get strange behavior like one effect is applied to another sprite, etc.
KennyCason
@KennyCason: Unfortunately, the link in your question goes to where there's a small fragment of code, less than you've posted above.
David Thornley
hmm. that should not be the case there is a Zip file containing the whole source as well as four links to the individual source files on that page (look above the code fragment). Thanks for looking.
KennyCason
+1  A: 

You may consider the ConstrOpt paradigm. I first heard about this when reading the XML-RPC C/C++ lib documentation here: http://xmlrpc-c.sourceforge.net/doc/libxmlrpc++.html#constropt

Basically the idea is similar to yours, but the "ConstrOpt" paradigm uses a subclass of the one you want to instantiate. This subclass is then instantiated on the stack with default options and then the relevant parameters are set with the "reference-chain" in the same way as you do.

The constructor of the real class then uses the constrOpt class as the only constructor parameter.

This is not the most efficient solution, but can help to get a clear and safe API design.

IanH
Thanks, I have seen that method before in some Java code. I know my question was very long winded but the problem I was having with my method of initialization was towards the bottom :)
KennyCason
+4  A: 

One note unrelated to your question, the statement A a = A(); probably isn't doing what you expect. In C++, objects aren't reference types that default to null, so this statement is almost never correct. You probably want just A a;

A a creates a new instance of A, but the = A() part invokes A's copy constructor with a temporary default constructed A. If you had done just A a; it would have just created a new instance of A using the default constructor.

If you don't explicitly implement your own copy constructor for a class, the compiler will create one for you. The compiler created copy constructor will just make a carbon copy of the other object's data; this means that if you have any pointers, it won't copy the data pointed to.

So, essentially, that line is creating a new instance of A, then constructing another temporary instance of A with the default constructor, then copying the temporary A to the new A, then destructing the temporary A. If the temporary A is acquiring resources in it's constructor and de-allocating them in it's destructor, you could run into issues where your object is trying to use data that has already been deallocated, which is undefined behavior.

Take this code for example:

struct A {
    A() { 
        myData = new int;
        std::cout << "Allocated int at " << myData << std::endl;
    }
    ~A() { 
        delete myData; 
        std::cout << "Deallocated int at " << myData << std::endl;
    }
    int* myData;
};

A a = A();
cout << "a.myData points to " << a.myData << std::endl;

The output will look something like:

Allocated int at 0x9FB7128
Deallocated int at 0x9FB7128
a.myData points to 0x9FB7128

As you can see, a.myData is pointing to an address that has already been deallocated. If you attempt to use the data it points to, you could be accessing completely invalid data, or even the data of some other object that took it's place in memory. And then once your a goes out of scope, it will attempt to delete the data a second time, which will cause more problems.

dauphic
A a = A() equals to A a(A()).in both cases, temporal object get constructed first before the named object.
YeenFei
Oh wow. I definitely overlooked that. thanks for the clarification, I will try it out when I get home. but +1 because I know for sure that that was definitely one of the issues!
KennyCason
+2  A: 

If you really, really hate calling lots of set functions, one after the other, then you may enjoy the following code, For most people, this is way overkill for the 'problem' solved.

This code demonstrates how to create a set function that can accept set classes of any number in any order.

#include "stdafx.h"
#include <stdarg.h>

// Base class for all setter classes
class cSetterBase
{
public:
    // the type of setter
    int myType;
    // a union capable of storing any kind of data that will be required
    union data_t {
        int i;
        float f;
        double d;
    } myValue;

    cSetterBase( int t ) : myType( t ) {}
};

// Base class for float valued setter functions
class cSetterFloatBase : public cSetterBase
{
public:
    cSetterFloatBase( int t, float v ) :
        cSetterBase( t )
        { myValue.f = v; }
};

// A couple of sample setter classes with float values
class cSetterA : public cSetterFloatBase
{
public:
    cSetterA( float v ) :
        cSetterFloatBase( 1, v )
        {}
};
// A couple of sample setter classes with float values
class cSetterB : public cSetterFloatBase
{
public:
    cSetterB( float v ) :
        cSetterFloatBase( 2, v )
        {}
};


// this is the class that actually does something useful
class cUseful
{
public:
    // set attributes using any number of setter classes of any kind
    void Set( int count, ... );

    // the attributes to be set
    float A, B;
};

    // set attributes using any setter classes
void cUseful::Set( int count, ... )
{
    va_list vl;
   va_start( vl, count );

     for( int kv=0; kv < count; kv++ ) {
        cSetterBase s = va_arg( vl, cSetterBase );
        cSetterBase * ps = &s;
        switch( ps->myType ) {
        case 1:
            A = ((cSetterA*)ps)->myValue.f; break;
        case 2:
            B = ((cSetterB*)ps)->myValue.f; break;
        }
     }
     va_end(vl);
}


int _tmain(int argc, _TCHAR* argv[])
{
    cUseful U;
    U.Set( 2,  cSetterB( 47.5 ), cSetterA( 23 ) );
    printf("A = %f B = %f\n",U.A, U.B );
    return 0;
}
ravenspoint
haha that is definitely overkill in my case :) but that is very insightful. At one point in time I had actually been curious about something like this.
KennyCason