Единый класс, который объединяет константный и неконстантный элемент справочных данных - PullRequest
0 голосов
/ 21 февраля 2012

Вот набор классов C ++, которые реализуют своего рода шаблон адаптера:

#include <iostream>

class Cfoo
{
public:
    explicit Cfoo(int i):i_(i){}
    void SetI(int i){ i_ = i; }
    int GetI()const{ return(i_); }
private:
    int i_;
};

class CfooHolderConst
{
public:
    explicit  CfooHolderConst(const Cfoo& foo):foo_(foo){}
    int GetI()const{ return( foo_.GetI() ); }
private:
    const Cfoo& foo_;
};

class CfooHolderNonConst
{
public:
    explicit CfooHolderNonConst(Cfoo& foo):foo_(foo){};
    int GetI()const{ return( foo_.GetI() ); }
    void SetI(int i){ foo_.SetI(i); }
private:
    Cfoo& foo_;
};

int main(  int argc, char* argv[] )
{
    const Cfoo myConstFoo(42);
    CfooHolderConst myConstFooHolder(myConstFoo);
    std::cout << myConstFooHolder.GetI() << std::endl;

    Cfoo myNonConstFoo(1);
    CfooHolderNonConst myNonConstFooHolder(myNonConstFoo);
    myNonConstFooHolder.SetI(42);
    std::cout << myConstFooHolder.GetI() << std::endl;

    return(0);
}

Я хочу объединить CfooHolderNonConst и CFooHolderConst в один класс или, в случае неудачи, наследовать один от другого.Ссылка на Cfoo является проблемой здесь, потому что в CFooHolderConst он должен быть определен как const Cfoo &, в то время как в CfooHolderNonConst это должен быть Cfoo &.

Это похоже на проблему с интегратором / const_iterator здесь: Как избежать дублирования кода при реализации константных и неконстантных итераторов?

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

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

Ответы [ 5 ]

1 голос
/ 21 февраля 2012

Вы можете создать шаблонный класс, который предоставит вам функциональность const как для const, так и для неконстантных версий класса, а затем наследует для расширения неконстантной версии функциональностью для изменения члена:

class Cfoo
{
public:
    explicit Cfoo(int i):i_(i){}
    void SetI(int i){ i_ = i; }
    int GetI()const{ return(i_); }
private:
    int i_;
};

class CfooHolderNonConst;

template<class Foo>
class CFooHolder
{
    friend class CfooHolderNonConst;
public:
    explicit  CFooHolder(Foo& foo):foo_(foo){}
    int GetI()const{ return( foo_.GetI() ); }
private:
    Foo& foo_;
};

typedef CFooHolder<const Cfoo> CfooHolderConst;

class CfooHolderNonConst: public CFooHolder<Cfoo>
{
public:
    explicit CfooHolderNonConst(Cfoo& foo):CFooHolder(foo){};
    void SetI(int i){ foo_.SetI(i); }
};
1 голос
/ 21 февраля 2012

Да, это можно сделать:

template< typename T > CHolderReader
{
 public: 
    explicit  CHolderBase( T& t):t_(t){}
    int Get()const { return t_.GetI(); }

 protected:
    ~CHolderReader() {}

 protected:
    T& t_;
};

template< typename T > CHolderReaderWriter : public CHolderReader< T >
{
public:
   void Set( int i)
   {
       t_.SetI(i);
   }
};

typedef CHolderReader<const Cfoo> CFooHolderConst;
typedef CHolderReaderWriter<Cfoo> CFooHolderNonConst;    

На самом деле это должен быть пример, в котором вы переносите получение базовых данных в их постоянное или неконстантное состояние. Reader содержит неконстантную ссылку, если только шаблонный тип не является константным, но не позволяет вам писать в него, поэтому вы можете расширить его, как с CHolderReaderWriter, когда вам нужно записать в него.

0 голосов
/ 21 февраля 2012

Есть ли конкретная причина, по которой вы не можете просто использовать FooHolder для неконстантного (изменяемого) доступа и const FooHolder для константного доступа?

Вы не можете вызывать неконстантный метод (например, SetI) для константного объекта, поэтому кажется, что он делает то, что вам нужно. Очевидно, вам нужно создать объект-держатель из неконстантного Cfoo первоначально, хотя.

пример:

class Cfoo
{
public:
    explicit Cfoo(int i) : i_(i) {}
    void SetI(int i) { i_ = i; }
    int GetI() const { return(i_); }
private:
    int i_;
};

class CfooHolder
{
public:
    explicit CfooHolder(Cfoo& foo) : foo_(foo) {};
    void SetI(int i) { foo_.SetI(i); }
    int GetI() const { return( foo_.GetI() ); }
private:
    Cfoo& foo_;
};

void bar(CfooHolder &holder, int i)
{
    holder.SetI(i); // fine
}

void bar(CfooHolder const &constholder, int i)
{
    holder.SetI(i);
    // error: method exists, but I can't call it here
}
0 голосов
/ 21 февраля 2012

Если вы действительно хотите иметь тот же класс (при условии, что семантика все еще имеет смысл), то я думаю, что вы хотите что-то вроде:

const Cfoo f1( 5 );
const CfooHolder h1( f1 );

Cfoo f2( 0 );
CfooHolder h2( f2 );

Я думаю, что вы надеетесь, что C ++ сделаетследующие решения: a) Обрабатывать объект Cfoo как const, если он был const, или неконстантным, если он был неконстантным.Ключом является как определение Cfoo , так и определение CfooHolder .Если Cfoo равен const, тогда CfooHolder должен быть объявлен const, иначе он не сможет скомпилироваться.Если Cfoo не является константой, то вы можете создать CfooHolder , которые могут быть как const, так и не- const.б) метод SetI() должен перестать компилироваться при использовании в const CfooHolder объекте.В приведенном выше примере h1.SetI( 6 ); не должен компилироваться.

Мой ответ таков: если a) сработало, то b) также сработает автоматически.Проблема заключается в достижении а), что, насколько я знаю, невозможно.

Чтобы это работало, атрибут должен быть сделан const или неконстантным в зависимости от объекта его класса.постоянный или неконстантный.Хотя объект класса может изменить это «состояние», атрибуты остаются неизменными.Но вы можете использовать методы const только тогда, когда объект этого класса равен const (например, когда параметр передается по постоянной ссылке).Таким образом, C ++ не будет поддерживать это, потому что он не работает таким образом.

Другой возможностью было бы позволить самому атрибуту быть константным и неконстантным одновременно, что не имеет смысла.

Краткий ответ: это невозможно, и будет повторение кода.Если вы действительно хотите избежать этого, а обертка достаточно сложна, чтобы беспокоиться, единственный способ - создать общий держатель, а затем постоянные и непостоянные обертки вокруг общего держателя, избегая повторения до минимума.

class CfooHolder
{
public:
    explicit CfooHolder(Cfoo& foo):foo_(foo){};
    int GetI()const{ return( foo_.GetI() ); }
    virtual void SetI(int i){ foo_.SetI(i); }
protected:
    Cfoo& foo_;
};

class CfooHolderNonConst : public CfooHolder {
public:
    explicit CfooHolderNonConst(Cfoo& foo):CfooHolder(foo){};
};

class CfooHolderConst: public CfooHolder
{
public:
    explicit  CfooHolderConst(const Cfoo& foo):CfooHolder(const_cast<Cfoo &>( foo )){}
    void SetI(int i){ throw std::runtime_error( "Don't write to me!" ); }
};

Он не идеален, но работает на заявленных условиях.Метод SetI() выдаст ошибку времени выполнения, но если объект CfooHolderConst объявлен как const, то вызов SetI() даже не скомпилируется.

Надеюсь, этопомогает.

0 голосов
/ 21 февраля 2012

Я думаю, что было бы неплохо иметь как константный, так и неконстантный интерфейс как отдельные классы.

Они предоставляют разные интерфейсы своим пользователям и имеют разную семантику. Дублирование также минимально в вашем примере.

...