67

我用 C# 编程已经有一段时间了,现在我想复习一下我的 C++ 技能。

上课:

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

什么是最好的方法(我只想允许读取 name_ 字段):

  • 使用 getter 方法:inline const std::string& name() const { return name_; }
  • 公开该字段,因为它是一个常数

谢谢。

4

8 回答 8

80

使用 getter 方法对于长期存在的类来说是更好的设计选择,因为它允许您在将来用更复杂的东西替换 getter 方法。尽管对于 const 值似乎不太可能需要这样做,但成本很低,可能的好处很大。

顺便说一句,在 C++ 中,为成员赋予相同名称的 getter 和 setter 是一个特别好的主意,因为将来您可以实际更改这对方法:

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;
    ...
};

客户端代码当然需要重新编译,但不需要更改语法!显然,这种转换同样适用于 const 值,其中只需要一个 getter。

于 2009-04-17T15:26:34.213 回答
48

公开非常量字段往往不是一个好主意,因为这样就很难强制错误检查约束和/或在未来对值更改添加副作用。

在您的情况下,您有一个 const 字段,因此上述问题不是问题。将其设为公共字段的主要缺点是您锁定了底层实现。例如,如果将来您想将内部表示更改为 C 字符串或 Unicode 字符串或其他内容,那么您将破坏所有客户端代码。使用 getter,您可以转换为现有客户端的旧表示,同时通过新的 getter 向新用户提供更新的功能。

我仍然建议使用像您上面放置的那样的 getter 方法。这将最大限度地提高您未来的灵活性。

于 2009-04-17T15:08:58.077 回答
24

顺便说一句,在 C++ 中,有一个 const 引用成员有点奇怪。您必须在构造函数列表中分配它。谁拥有该对象的实际内存,它的生命周期是多少?

至于风格,我同意其他人的观点,即您不想暴露自己的隐私。:-) 我喜欢 setter/getter 的这种模式

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");
于 2009-04-18T03:07:25.573 回答
8

我认为现在的 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”的唯一时间是在您编写自定义 getter/setter 方法时。

于 2013-12-09T16:24:27.217 回答
5

即使名称是不可变的,您可能仍希望选择计算它而不是将其存储在字段中。(我意识到这对于“名称”来说不太可能,但让我们针对一般情况。)因此,即使是常量字段也最好包裹在 getter 中:

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

请注意,如果您要更改getName()为返回计算值,则它无法返回 const ref。没关系,因为它不需要对调用者进行任何更改(模重新编译。)

于 2009-04-17T15:10:55.793 回答
3

避免使用公共变量,除了本质上是 C 风格结构的类。进入这不是一个好习惯。

一旦你定义了类接口,你可能永远无法改变它(除了添加它),因为人们会在它之上构建并依赖它。将变量公开意味着您需要拥有该变量,并且您需要确保它具有用户需要的内容。

现在,如果您使用 getter,您承诺提供一些信息,这些信息当前保存在该变量中。如果情况发生变化,并且您不想一直维护该变量,则可以更改访问权限。如果需求发生变化(并且我看到了一些非常奇怪的需求变化),并且您主要需要此变量中的名称,但有时需要该变量中的名称,您只需更改 getter。如果你将变量公开,你就会被它困住。

这不会总是发生,但我发现写一个快速的 getter 比分析情况看我是否会后悔将变量公开(并冒着以后出错的风险)要容易得多。

将成员变量设为私有是一个好习惯。任何有代码标准的商店都可能会禁止将偶尔的成员变量公开,任何有代码审查的商店都可能会因此批评你。

每当写作的便利性真的无关紧要时,养成更安全的习惯。

于 2009-04-17T15:50:35.653 回答
1

从多个 C++ 源中收集想法并将其放入一个不错的、仍然非常简单的 C++ 中的 getter/setter 示例:

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 伊薇特 <3

于 2018-10-25T21:04:20.510 回答
0

来自设计模式理论;“封装变化的东西”。通过定义“吸气剂”,可以很好地遵守上述原则。因此,如果将来成员的实现表示发生变化,则可以在从“getter”返回之前对成员进行“按摩”;暗示在进行“getter”调用的客户端没有代码重构。

问候,

于 2009-04-17T17:04:53.647 回答