C ++ стиль кодирования геттеров / сеттеров - PullRequest
65 голосов
/ 17 апреля 2009

Я некоторое время программировал на C #, и теперь я хочу освежить свои навыки в C ++.

Имея класс:

class Foo
{
    const std::string& name_;
    ...
};

Какой будет наилучший подход (я только хочу разрешить доступ на чтение к полю name_):

  • используйте метод получения: inline const std::string& name() const { return name_; }
  • сделать поле общедоступным, поскольку оно является константой

Спасибо.

Ответы [ 8 ]

77 голосов
/ 17 апреля 2009

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

Кроме того, в C ++ особенно полезно дать и получателю, и установщику для члена одно и то же имя , поскольку в будущем вы сможете изменить пару методов:

class Foo {
public:
    std::string const& name() const;          // Getter
    void name(std::string const& newName);    // Setter
    ...
};

В одну общедоступную переменную-член, которая определяет operator()() для каждого:

// This class encapsulates a fancier type of name
class fancy_name {
public:
    // Getter
    std::string const& operator()() const {
        return _compute_fancy_name();    // Does some internal work
    }

    // Setter
    void operator()(std::string const& newName) {
        _set_fancy_name(newName);        // Does some internal work
    }
    ...
};

class Foo {
public:
    fancy_name name;
    ...
};

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

46 голосов
/ 17 апреля 2009

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

В вашем случае у вас есть поле const, поэтому вышеуказанные проблемы не являются проблемой. Основным недостатком сделать его публичным полем является то, что вы блокируете базовую реализацию. Например, если в будущем вы захотите изменить внутреннее представление на C-строку или строку Unicode или что-то еще, то вы нарушите весь клиентский код. Используя геттер, вы можете преобразовать его в устаревшее представление для существующих клиентов, одновременно предоставляя новые функции новым пользователям через новый геттер.

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

21 голосов
/ 18 апреля 2009

Кроме того, в C ++ несколько странно иметь ссылочный член const. Вы должны назначить его в списке конструкторов. Кому принадлежит собственно память об этом объекте и каков его срок жизни?

Что касается стиля, я согласен с другими, что вы не хотите выставлять своих рядовых. :-) Мне нравится этот шаблон для сеттеров / геттеров

class Foo
{
public:
  const string& FirstName() const;
  Foo& FirstName(const string& newFirstName);

  const string& LastName() const;
  Foo& LastName(const string& newLastName);

  const string& Title() const;
  Foo& Title(const string& newTitle);
};

Таким образом, вы можете сделать что-то вроде:

Foo f;
f.FirstName("Jim").LastName("Bob").Title("Programmer");
8 голосов
/ 09 декабря 2013

Я думаю, что подход C ++ 11 будет больше похож на это сейчас.

#include <string>
#include <iostream>
#include <functional>

template<typename T>
class LambdaSetter {
public:
    LambdaSetter() :
        getter([&]() -> T { return m_value; }),
        setter([&](T value) { m_value = value; }),
        m_value()
    {}

    T operator()() { return getter(); }
    void operator()(T value) { setter(value); }

    LambdaSetter operator=(T rhs)
    {
        setter(rhs);
        return *this;
    }

    T operator=(LambdaSetter rhs)
    {
        return rhs.getter();
    }

    operator T()
    { 
        return getter();
    }


    void SetGetter(std::function<T()> func) { getter = func; }
    void SetSetter(std::function<void(T)> func) { setter = func; }

    T& GetRawData() { return m_value; }

private:
    T m_value;
    std::function<const T()> getter;
    std::function<void(T)> setter;

    template <typename TT>
    friend std::ostream & operator<<(std::ostream &os, const LambdaSetter<TT>& p);

    template <typename TT>
    friend std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p);
};

template <typename T>
std::ostream & operator<<(std::ostream &os, const LambdaSetter<T>& p)
{
    os << p.getter();
    return os;
}

template <typename TT>
std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p)
{
    TT value;
    is >> value;
    p.setter(value);
    return is;
}


class foo {
public:
    foo()
    {
        myString.SetGetter([&]() -> std::string { 
            myString.GetRawData() = "Hello";
            return myString.GetRawData();
        });
        myString2.SetSetter([&](std::string value) -> void { 
            myString2.GetRawData() = (value + "!"); 
        });
    }


    LambdaSetter<std::string> myString;
    LambdaSetter<std::string> myString2;
};

int _tmain(int argc, _TCHAR* argv[])
{
    foo f;
    std::string hi = f.myString;

    f.myString2 = "world";

    std::cout << hi << " " << f.myString2 << std::endl;

    std::cin >> f.myString2;

    std::cout << hi << " " << f.myString2 << std::endl;

    return 0;
}

Я протестировал это в Visual Studio 2013. К сожалению, чтобы использовать базовое хранилище внутри LambdaSetter, мне нужно было предоставить общедоступный метод доступа «GetRawData», который может привести к нарушению инкапсуляции, но вы можете оставить его и предоставить свой контейнер хранения для T или просто убедитесь, что вы используете «GetRawData» только тогда, когда пишете пользовательский метод получения / установки.

5 голосов
/ 17 апреля 2009

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

class Foo {
    public:
        const std::string& getName() const {return name_;}
    private:
        const std::string& name_;
};

Обратите внимание, что если вы измените getName() для возврата вычисленного значения, он не сможет вернуть const ref. Это нормально, потому что это не потребует никаких изменений в вызывающих программах (по модулю перекомпиляции.)

3 голосов
/ 17 апреля 2009

Избегайте открытых переменных, за исключением классов, которые по сути являются структурами в стиле C. Это просто плохая практика.

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

Теперь, если вы используете геттер, вы обещаете предоставить некоторую информацию, которая в настоящее время хранится в этой переменной. Если ситуация изменится, и вы не захотите постоянно поддерживать эту переменную, вы можете изменить доступ. Если требования меняются (и я видел некоторые довольно странные изменения требований), и вам в основном нужно имя, которое находится в этой переменной, но иногда в этой переменной, вы можете просто изменить геттер. Если вы сделали переменную общедоступной, вы застряли бы в ней.

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

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

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

1 голос
/ 26 октября 2018

Собрал идеи из нескольких источников C ++ и поместил их в хороший, но довольно простой пример для методов получения / установки в C ++:

class Canvas { public:
    void resize() {
        cout << "resize to " << width << " " << height << endl;
    }

    Canvas(int w, int h) : width(*this), height(*this) {
        cout << "new canvas " << w << " " << h << endl;
        width.value = w;
        height.value = h;
    }

    class Width { public:
        Canvas& canvas;
        int value;
        Width(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } width;

    class Height { public:
        Canvas& canvas;
        int value;
        Height(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } height;
};

int main() {
    Canvas canvas(256, 256);
    canvas.width = 128;
    canvas.height = 64;
}

Выход:

new canvas 256 256
resize to 128 256
resize to 128 64

Вы можете проверить это онлайн здесь: http://codepad.org/zosxqjTX

PS: FO Yvette <3 </p>

0 голосов
/ 17 апреля 2009

Из теории шаблонов проектирования; «инкапсулировать то, что меняется». При определении «добытчика» существует хорошая приверженность вышеуказанному принципу. Таким образом, если представление-представление элемента изменится в будущем, элемент может быть «массирован» перед возвратом из «получателя»; подразумевает отсутствие рефакторинга кода на стороне клиента, где выполняется вызов getter.

С уважением,

...