Стиль кодирования/компоновщика С++

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

Наличие класса:

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

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

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

Спасибо.

Ответ 1

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

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

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

Ответ 2

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

Как в стороне, на С++, особенно хорошая идея дать и getter и setter для одного и того же имени, так как в будущем вы можете фактически изменить пару методов:

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

В одну переменную public member, которая определяет 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;
    ...
};

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

Ответ 3

Как в стороне, в С++, несколько странно иметь элемент ссылки 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");

Ответ 4

Я думаю, что теперь подход С++ 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", когда вы пишете пользовательский метод getter/setter.

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

Ответ 8

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

Привет,