Вопрос

Если я не ошибаюсь, шаблон getter / setter - это обычный шаблон, используемый для двух вещей:

  1. Создать закрытую переменную так, чтобы ее можно было использовать, но никогда не изменять, всего лишь предоставив getVariable метод (или, реже, только модифицируемый, предоставляя только setVariable способ).
  2. Чтобы быть уверенным, что в будущем, если у вас возникнет проблема, хорошим решением которой было бы просто обработать переменную до того, как она войдет в класс и / или выйдет из него, вы можете обработать переменную, используя реальную реализацию методов getter и setter вместо простого возврата или установки значений.Таким образом, изменение не распространяется на остальную часть кода.

Вопрос № 1:Мне не хватает каких-либо средств доступа или какие-либо из моих предположений неверны?Я не уверен, что я прав в этих вопросах.

Вопрос № 2:Существуют ли какие-либо преимущества шаблона, которые могут избавить меня от необходимости писать средства доступа для моих переменных-членов?Я ничего такого не нашел.

Вопрос № 3:Будет ли следующий шаблон класса хорошим способом реализации средства получения без необходимости фактического написания средства доступа?

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend class TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    template <typename ... Args>
    Getter(Args args) : value(args ...) {} // Uses C++0x

    T get() { return value; }

protected:
    T value;
};

class Window
{
public:
    Getter<uint32_t,Window> width;
    Getter<uint32_t,Window> height;

    void resize(uint32_t width,uint32_t height)
    {
        // do actual window resizing logic

        width.value = width; // access permitted: Getter befriends Window
        height.value = height; // same here
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method

    // This works: Getter::get() is public
    std::cout << "Current window size: " << win.width.get() << 'x' << win.height.get() << ".\n";

    // This doesn't work: Getter::value is private
    win.width.value = 640;
    win.height.value = 480;
}

Мне это кажется справедливым, и я мог бы даже переопределить get логика с использованием какой-либо другой уловки с частичной специализацией шаблона.То же самое может быть применено к какому-то типу шаблонов классов Setter или даже GetterSetter .

О чем вы думаете?

Это было полезно?

Решение

Хотя решение является аккуратным с точки зрения реализации, с архитектурной точки зрения оно находится только на полпути к цели.Смысл шаблона получения / настройки состоит в том, чтобы предоставить классу контроль над его данными и уменьшить связь (т.е.другой класс , знающий как данные сохранены).Это решение обеспечивает первое, но не совсем последнее.

Фактически, другой класс теперь должен знать две вещи - имя переменной и метод в получателе (т.е. .get()) вместо одного - например getWidth().Это приводит к усилению сцепления.

Сказав все это, это расщепляет пресловутые архитектурные волоски.В конце концов, это не так уж и важно.

Редактировать Хорошо, теперь для шуток и смешков, вот версия геттера, использующего операторы, так что вам не нужно делать .value или .get()

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    operator T()
    {
        return value;
    }

protected:
    T value;

    T& operator=( T other )
    {
       value = other;
       return value;  
    }


};

class Window
{
public:
    Getter<int,Window> _width;
    Getter<int,Window> _height;

    void resize(int width,int height)
    {
        // do actual window resizing logic
        _width = width; //using the operator
        _height = height; //using the operator
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method
    int w2 = win._width; //using the operator
    //win._height = 480; //KABOOM
}

Редактировать Исправлен жестко закодированный оператор присваивания.Это должно работать достаточно хорошо, если сам тип имеет оператор присваивания.По умолчанию они есть в структурах, поэтому для простых это должно работать "из коробки".

Для более сложных классов вам нужно будет реализовать оператор присваивания, который является достаточно справедливым.С РВО и Копировать При записи оптимизация, это должно быть достаточно эффективным во время выполнения.

Другие советы

FWIW вот мое мнение по вашим вопросам:

  1. Обычно дело в том, что в установщике применяется бизнес-логика или другие ограничения.Вы также можете иметь вычисляемые или виртуальные переменные, отделив переменную экземпляра от методов доступа.
  2. Насколько мне известно, нет.Проекты, над которыми я работал, имели семейство макросов C для искоренения таких методов
  3. ДА;Я думаю, это довольно изящно.Я просто беспокоюсь, что это не стоит таких хлопот, это просто собьет с толку других разработчиков (еще одна концепция, которую они должны уместить в своей голове) и не сильно экономит на том, чтобы использовать такие методы вручную.

Поскольку Игорь Зевака опубликовал одну версию этого, я опубликую ту, которую написал давным-давно.Это немного отличается - в то время я заметил , что большинство реальное использование пар get / set (которые на самом деле что-то делали) заключалось в том, чтобы принудительно удерживать значение переменной в пределах заранее определенного диапазона.Это немного более обширно, например, добавление операторов ввода-вывода, где extractor по-прежнему применяет определенный диапазон.В нем также есть немного кода теста / упражнения, чтобы показать общее представление о том, что он делает и как он это делает:

#include <exception>
#include <iostream>
#include <functional>

template <class T, class less=std::less<T> >
class bounded {
    const T lower_, upper_;
    T val_;

    bool check(T const &value) {
        return less()(value, lower_) || less()(upper_, value);
    }

    void assign(T const &value) {
        if (check(value))
            throw std::domain_error("Out of Range");
        val_ = value;
    }

public:
    bounded(T const &lower, T const &upper) 
        : lower_(lower), upper_(upper) {}

    bounded(bounded const &init) 
        : lower_(init.lower), upper_(init.upper)
    { 
        assign(init); 
    }

    bounded &operator=(T const &v) { assign(v);  return *this; }

    operator T() const { return val_; }

    friend std::istream &operator>>(std::istream &is, bounded &b) {
        T temp;
        is >> temp;

        if (b.check(temp))
            is.setstate(std::ios::failbit);
        else
            b.val_ = temp;
        return is;
    }
};


#ifdef TEST

#include <iostream>
#include <sstream>

int main() {
    bounded<int> x(0, 512);

    try {
        x = 21;
        std::cout << x << std::endl;

        x = 1024;
        std::cout << x << std::endl;
    }

    catch(std::domain_error &e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    std::stringstream input("1 2048");
    while (input>>x)
        std::cout << x << std::endl; 

    return 0;
}

#endif
  1. Вы также можете использовать метод типа getter или setter для получения или задания вычислимых значений, во многом аналогично тому, как свойства используются в других языках, таких как C#

  2. Я не могу придумать разумный способ абстрагироваться от получения и настройки неизвестного количества значений / свойств.

  3. Я недостаточно знаком со стандартом C ++ ox, чтобы комментировать.

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

http://www.ddj.com/cpp/184402053/

А теперь вопрос, а что, если вам нужен setter также.

Не знаю, как у вас, но у меня, как правило, (примерно) два типа занятий:

  • класс для логики
  • капли

Большие двоичные объекты - это просто свободные наборы всех свойств бизнес-объекта.Например, a Person будет иметь surname, firstname, несколько адресов, несколько профессий...итак Person может не иметь логики.

Для больших двоичных объектов я обычно использую канонический приватный атрибут + getter + setter , поскольку он абстрагирует фактическую реализацию от клиента.

Однако, хотя ваш шаблон (и его эволюция Игорем Зевекой) действительно хороши, они не решают проблему настройки и не решают двоичная совместимость проблемы.

Думаю, я бы, вероятно, прибегнул к макросам...

Что - то вроде:

// Interface
// Not how DEFINE does not repeat the type ;)
#define DECLARE_VALUE(Object, Type, Name, Seq) **Black Magic Here**
#define DEFINE_VALUE(Object, Name, Seq) ** Black Magic Here**

// Obvious macros
#define DECLARE_VALUER_GETTER(Type, Name, Seq)\
   public: boost::call_traits<Type>::const_reference Name() const

#define DEFINE_VALUE_GETTER(Object, Name)\
   boost::call_traits<Name##_type>::const_reference Object::Name ()const\
   { return m_##Name; }

#define DECLARE_VALUE_SETTER(Object, Type, Name)\
   public: Type& Name();\
   public: Object& Name(boost::call_traits<Type>::param_type i);

#define DEFINE_VALUE_SETTER(Object, Name)\
   Name##_type& Object::Name() { return m_##Name; }\
   Object& Object::Name(boost::call_traits<Name##_type>::param_type i)\
   { m_##Name = i; return *this; }

Который был бы использован как:

// window.h
DECLARE_VALUE(Window, int, width, (GETTER)(SETTER));

// window.cpp
DEFINE_VALUE(Window, width, (GETTER)); // setter needs a bit of logic

Window& Window::width(int i) // Always seems a waste not to return anything!
{ 
  if (i < 0) throw std::logic_error();
  m_width = i;
  return *this;
} // Window::width

С небольшим количеством магии препроцессора это сработало бы вполне хорошо!

#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/rem.hpp>

#define DECLARE_VALUE_ITER(r, data, elem)\
  DECLARE_VALUE_##elem ( BOOST_PP_TUPLE_REM(3)(data) )

#define DEFINE_VALUE_ITER(r, data, elem)\
  DEFINE_VALUE_##elem ( BOOST_PP_TUPLE_REM(2)(data) )

#define DECLARE_VALUE(Object, Type, Name, Seq)\
   public: typedef Type Name##_type;\
   private: Type m_##Name;\
   BOOST_PP_SEQ_FOREACH(DECLARE_VALUE_ITER, (Object, Type, Name), Seq)

#define DEFINE_VALUE(Object, Name, Seq)\
   BOOST_PP_SEQ_FOREACH(DEFINE_VALUE_ITER, (Object, Name), Seq)

Ладно, не типо-сейф и все такое, но:

  • я думаю, это разумный набор макросов
  • он прост в использовании, в конце концов, пользователю приходится беспокоиться только о 2 макросах, хотя, как и в случае с шаблонами, ошибки могут быть очень сложными
  • использование boost.call_traits для повышения эффективности (const& / выбор значения)
  • там больше функциональности:дуэт добытчика/сеттера

  • к сожалению, это набор макросов...и не буду жаловаться, если вы когда-нибудь

  • это наносит ущерб средствам доступа (общедоступным, защищенным, частным), поэтому лучше не распространять его по всему классу

Тогда вот канонический пример:

class Window
{
  // Best get done with it
  DECLARE_VALUE(Window, int, width, (GETTER));
  DECLARE_VALUE(Window, int, height, (GETTER));

// don't know which is the current access level, so better define it
public:

};

Вы решаете не ту проблему.В хорошо разработанном приложении получатели и установщики должны быть Редкий, не автоматизирован.Значимый класс предоставляет какой-то абстракция.Это не просто набор элементов, это моделирует концепцию, которая представляет собой нечто большее, чем просто сумму входящих в нее переменных.И, как правило, даже не имеет смысла раскрывать отдельных участников.

Класс должен предоставлять операции, которые имеют смысл для концепции, которую он моделирует.Большинство переменных-членов существуют для поддержания этой абстракции, для хранения состояния, которое вам нужно.Но обычно к нему не следует обращаться напрямую. Вот почему в первую очередь это закрытый член класса.

Вместо того, чтобы искать более простые способы писать car.getFrontLeftWheel(), спросите себя, зачем пользователю класса вообще понадобилось переднее левое колесо.Вы обычно управляете этим рулем непосредственно во время движения?Предполагается, что машина возьмет на себя все хлопоты по вращению колес за вас, не так ли?

Это то место, где я думаю #defines все еще полезны.

Версия шаблона сложна и трудна для понимания - версия define очевидна

#define Getter(t, n)\
     t n;\
     t get_##n() { return n; }

class Window
{
    Getter(int, height);
}

Я уверен, что у меня немного неправильный синтаксис, но вы поняли суть.

Если бы был хорошо известный набор шаблонов, скажем, в boost, то я бы использовал их.Но я бы не стал писать свой собственный.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top