Вопрос

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

Имея класс:

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

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

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

Спасибо.

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

Решение

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

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

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

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

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

Кроме того, в 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;
    ...
};

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

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

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

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");

Я думаю, что сейчас подход 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», когда вы пишете собственный метод получения/установки.

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

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

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

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

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

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

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

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

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

Собрал идеи из нескольких источников 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

ПС:ФО Иветта <3

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

С уважением,

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