C++11;非静态数据成员初始化能否访问其他数据成员?

6

我非常喜欢C#中的属性概念,作为一个小的副业项目,我一直在尝试将其实现到C++中。我遇到了这个例子https://dev59.com/k2025IYBdhLWcg3w1JiG#5924594 ,看起来相当不错,但我不禁想到,使用lambda表达式和非静态数据成员初始化可能会使这个想法能够使用一些非常好的语法。这是我的实现:

#include <iostream>
#include <functional>

using namespace std;


template< typename T >
class property {

public:
    property(function<const T&(void)> getter, function<void(const T&)> setter)
        : getter_(getter),
          setter_(setter)
    {};

    operator const T&() {
        return getter_();
    };

    property<T>& operator=(const T& value) {
        setter_(value);
    }

private:
    function<const T&(void)> getter_;
    function<void(const T&)> setter_;

};


class Foobar {

public:
    property<int> num {
        [&]() { return num_; },
        [&](const int& value) { num_ = value; }
    };

private:
    int num_;

};


int main() {
    // This version works fine...
    int myNum;
    property<int> num = property<int>(
        [&]() { return myNum; },
        [&](const int& value) { myNum = value; }
    );
    num = 5;

    cout << num << endl;  // Outputs 5
    cout << myNum << endl;  // Outputs 5 again.

    // This is what I would like to see work, if the property
    // member of Foobar would compile...
    // Foobar foo;
    // foo.num = 5;

    // cout << foo.num << endl;

    return 0;
}

我可以正常使用我的属性类(请参见main()中的示例),但是使用MinGW和g++4.7时,我的尝试将属性用作数据成员并不特别成功:

\property.cpp: In lambda function:
\property.cpp:40:7: error: invalid use of non-static data member 'Foobar::num_'

看起来我的属性实现的概念是可以工作的,但可能是徒劳无功,因为我不能从我的lambda函数中访问其他数据成员。我不确定标准如何定义我在这里尝试做什么,我完全没有运气,还是我在这里做错了?


3
不相关:您可能希望在构造函数中使用 getter_(std::move(getter)), setter_(std::move(setter)) 来支持只可移动的类型,并通常避免不必要的拷贝。 - R. Martinho Fernandes
问题:除了有趣之外,我认为一个简单的int&会更有用。你的解决方案相比于一个简单的引用有什么优势?(本身就是无用的...) - Matthieu M.
@R.MartinhoFernandes 我完全同意。property<> 本身也需要支持复制/移动语义,以便可以复制/移动具有属性的类。我希望在能够使概念工作之前尽可能保持实现最小化。不过还是谢谢! - Bret Kuhns
@MatthieuM。属性提供了与get/set方法相同的好处,但允许客户端使用更清晰的语法。一旦所有类都有get/set方法,情况可能会变得很丑陋,只需问任何Java开发人员(即obj1.setSomething(obj2.getSomething())obj1.something = obj2.something)。getter/setter允许对象在获取/设置时执行一些操作,例如对数据本身进行操作(翻译),或在获取/设置时执行幕后操作。例如,对于一个大对象的数据成员,我可以等到调用它的getter时再加载它,而不是在构造时强制加载。 - Bret Kuhns
@BretKuhns:我个人认为,大量的设置器等同于“数据包”。因此,我肯定会避免让它们看起来更酷......但这是很主观的。 - Matthieu M.
1个回答

3
您的属性是一个不同的对象(property<int>实例),与包含它的对象(Foobar实例)不同。因此,它的成员函数传递了一个不同的this,而不是您需要访问num_的那个。如果lambda表达式在Foobar的非静态成员函数中定义,它们将捕获该函数的this参数,并可以访问封闭对象的成员(明确地,作为this->num_)。但是lambda表达式在类中定义,在那里非静态数据成员实际上不存在。如果lambda表达式有权访问num_,那么哪个num_会被访问?是哪个Foobar实例的?
我看到的最简单的解决方案是使属性存储指向封闭对象的指针。这样,它就可以自由地访问其非静态成员。缺点是声明略微复杂(您必须执行property<int,Foobar> num),并且需要通过传递this指针来初始化属性。因此,您不能在类中执行此操作,它必须在构造函数的初始化列表中执行,从而抵消了C++11的数据成员初始化的优势。
在那一点上,this将可供lambda表达式捕获(按值而不是按引用!),因此如果您将属性的初始化移动到Foobar的构造函数中,则您的代码实际上可以通过最小更改来工作:
Foobar::Foobar():
    num {
        [this]() { return this->num_; },
        [this](const int& value) { this->num_ = value; }
    }
{
}

有人知道在类定义中,作为传递给任何构造函数的this是否可用于非静态成员初始化吗?我怀疑它不行,但如果可以,那么同样的构造将在类定义内部起作用。

我只想补充一点,这是一个更普遍的问题,即从对象访问封闭对象,而不在内部对象中存储指向外部对象的指针,在C++中一直很棘手。虽然可以做到,但看起来并不美观,我相信涉及的指针魔法会破坏严格别名规则... - cvoinescu
我在提出这个问题大约一个小时后就发现使用捕获[ this ]子句可以解决访问数据成员的问题,虽然我不确定为什么需要这样做,但你的答案为我澄清了这一点。谢谢!事实证明,您不需要将此移动到构造函数的初始化程序列表中。在我已经拥有的代码中更改捕获子句完全有效。 - Bret Kuhns
然而,还有一个问题悬而未决,与我的原始问题不太相关。由于您解释的原因,Lambda函数无法访问Foobar类的私有数据成员。我还没有完全弄清楚如何正确地将property<int>Foobar中设置为朋友以使其正常工作。将数据成员设置为public可以编译和运行,因此我认为友元关系是解决方案。 - Bret Kuhns
1
@Bret:无法将lambda函数添加为好友,因为在此上下文中无法声明其类型。 - ildjarn
@ildjarn 我认为,根据cvoinescu的解释,lambda表达式的上下文应该与property<>一起。我暂时无法尝试这个,但我不熟悉友元模板类。但是,也许你是正确的,lambda表达式本身就是问题所在,正如你指出的那样,这将是一个死胡同。 - Bret Kuhns
@Bret:没错,但是为了使其成为友元,上下文需要在包含类(Foobar)内。我认为这是不可实现的。 :-[ - ildjarn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接