C ++ Class design - легко инициализировать / создавать объекты - PullRequest
6 голосов
/ 04 августа 2010

Используя C ++, я создал Class, который имеет множество функций-сеттеров, а также различные функции, которые могут вызываться подряд во время выполнения.В итоге я получаю код, который выглядит следующим образом:

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

Не то, чтобы это плохо, но я не люблю набирать «a->» снова и снова.Поэтому я переписал свои определения классов следующим образом:

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

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

    // other functions

private:

    // vars
};

Итак, я мог бы начать свой класс следующим образом: (метод 1)

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

(который япредпочитаю как проще написать)Для более точной реализации этого вы можете увидеть мой класс SDL Sprite C ++, который я написал по адресу http://ken -soft.com /? P = 234

Кажется, все работает нормально.Тем не менее, я был бы заинтересован в любой обратной связи с этим подходом.Я заметил Одна проблема.Если я инициирую Мой класс, например: (метод 2)

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

Тогда у меня возникают различные проблемы с памятью, и иногда вещи не работают так, как должны (Вы можете увидеть это, изменивя инициирую все объекты Sprite в main.cpp моей демонстрации Sprite).Это нормально?Или поведение должно быть таким же? Редактировать сеттеры, прежде всего, облегчают мою жизнь при инициализации.Мой главный вопрос, как метод 1 и метод 2 ведут себя по-разному для меня?

Редактировать: Вот пример получения и установки:

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

int Sprite::getSpeed() {
    return speed;
}

Ответы [ 4 ]

4 голосов
/ 04 августа 2010

Одна заметка, не связанная с вашим вопросом, утверждение A a = A();, вероятно, не соответствует вашим ожиданиям. В C ++ объекты не являются ссылочными типами, которые по умолчанию имеют значение null, поэтому это утверждение почти никогда не является правильным. Вы, вероятно, хотите просто A a;

A a создает новый экземпляр A, но часть = A() вызывает конструктор копирования A с временным значением по умолчанию, созданным A. Если бы вы сделали просто A a;, он просто создал бы новый экземпляр A, используя конструктор по умолчанию.

Если вы явно не реализуете свой собственный конструктор копирования для класса, компилятор создаст его для вас. Созданный компилятором конструктор копирования просто сделает точную копию данных другого объекта; это означает, что если у вас есть какие-либо указатели, он не будет копировать указанные данные.

Таким образом, по существу, эта строка создает новый экземпляр A, затем создает другой временный экземпляр A с помощью конструктора по умолчанию, затем копирует временный A в новый A, затем уничтожает временный A. Если временный A получает ресурсы в своем конструкторе и отменяет их распределение в своем деструкторе, вы можете столкнуться с проблемами, когда ваш объект пытается использовать данные, которые уже были освобождены, что является неопределенным поведением.

Возьмите этот код, например:

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;

Вывод будет выглядеть примерно так:

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

Как видите, a.myData указывает на адрес, который уже был освобожден. Если вы попытаетесь использовать данные, на которые оно указывает, вы можете получить доступ к абсолютно недействительным данным или даже к данным какого-либо другого объекта, который занял свое место в памяти. И затем, как только ваш a выйдет из области видимости, он попытается удалить данные во второй раз, что вызовет больше проблем.

4 голосов
/ 04 августа 2010

То, что вы там реализовали, называется свободный интерфейс . Я в основном встречался с ними на языках сценариев, но нет причин, по которым вы не можете использовать их в C ++.

2 голосов
/ 04 августа 2010

Если вы действительно, очень не любите вызывать множество установленных функций, одну за другой, то вам может понравиться следующий код: Для большинства людей это слишком излишнее решение для «решенной» проблемы.

ЭтоКод демонстрирует, как создать функцию набора, которая может принимать наборы классов любого числа в любом порядке.

#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;
}
1 голос
/ 04 августа 2010

Вы можете рассмотреть парадигму ConstrOpt.Впервые я услышал об этом, когда читал документацию по XML для RP-C / C ++ здесь: http://xmlrpc -c.sourceforge.net / doc / libxmlrpc ++. Html # constropt

В основном идеяпохожа на вашу, но парадигма "ConstrOpt" использует подкласс того, который вы хотите создать.Затем этот подкласс создается в стеке с параметрами по умолчанию, а затем соответствующие параметры устанавливаются с помощью "reference-chain" таким же образом, как и вы.

Затем конструктор реального класса использует класс constrOptкак единственный параметр конструктора.

Это не самое эффективное решение, но может помочь получить ясный и безопасный дизайн API.

...