如何模拟C#的{get;在 C++ 中设置;}?

问题描述 投票:0回答:4

我正在尝试 lambda 函数,并设法在 C++ 中重新创建“get”功能。我可以在不使用括号的情况下获取函数的返回值。这是一个示例类,我在其中实现了这个:

using namespace std;

struct Vector2 {
    float x;
    float y;
    float length = [&]()-> float {return sqrt(x * x + y * y); }();
    float angle = [&]()-> float {return atan2(y, x); }();

    Vector2() : x(0), y(0) {}
    Vector2(float a, float b) : x(a), y(b) {}
    ~Vector2() {}
    Vector2(Vector2& other) : x(other.x), y(other.y) {}
    Vector2(Vector2&& other) = delete;
    void operator =(Vector2&& other) noexcept{
        x = other.x;
        y = other.y;
    }
};

int main()
{
    Vector2 vec = Vector2(10, 17);
    printf("%f\n%f\n%f\n%f\n", vec.x, vec.y, vec.length, vec.angle);
}

但是,我目前正在尝试重新创建 C# 具有的“设置”功能。但我失败了。我尝试添加这个:

void angle = [&](float a)->void {
    float l = length;
    x = cos(a) * l;
    y = sin(a) * l;
};

但是收到“不允许不完整的类型”错误。我不确定它是否应该是这样的,即使我没有收到错误。是否有可能在 C++ 中重新创建 C# 的“设置”功能?

我知道我可以使用一种方法

SetAngle(float a){...}
,但这不是重点。

c++
4个回答
5
投票

TL;博士:不要

你拥有的不是一个getter,它只是一个普通的数据成员,在对象初始化时计算一次。

一般来说,C++ 不支持 C# 风格的属性。通常的 C++ 风格的解决方案是仅使用一对成员函数(如果需要单独保存值,可能还使用一个数据成员),即

struct Vector2 {
    // ...

    float length() const { return sqrt(x * x + y * y); }
    void length(float l) {
        float angle = angle();
        float new_x = l * cos(angle);
        float new_y = l * sin(angle);
        x = new_x;
        y = new_y;
    }

    // ...
};

您可以获得接近 C# 风格的属性,但您总是会遇到它们不能完美工作的边缘情况。例如,以下内容在许多情况下都有效:

template <typename T>
class Property
{
private:
    std::function<T()> getter_;
    std::function<void(const T&)> setter_;

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

    operator T()
    {
        return getter_();
    }

    const T& operator=(const T& val)
    {
        setter_(val);
        return val;
    }
};

struct Vector2
{
    float x;
    float y;
    Property<float> length{
        [this]() { return sqrt(x * x + y * y); },
        [this](float l) {
            float new_x = l * cos(angle);
            float new_y = l * sin(angle);
            x = new_x;
            y = new_y;
        }
    }

    Property<float> angle{
        [this]() { return atan2(y, x); },
        [this](float a) {
            float l = length;
            x = cos(a) * l;
            y = sin(a) * l;
        }
    }

    // ...
};

int main() {
    Vector2 v;
    v.x = 1;
    v.y = 1;

    v.angle = std::numbers::pi / 2;
    std::cout << "(" << v.x << ", " << v.y << ")\n";
}

但这在边缘情况下仍然会崩溃,特别是当你将它与模板和/或

auto
类型推导混合时。例如:

Vector2 v;
v.x = 1;
v.y = 1;

auto old_angle = v.angle;
v.angle = std::numbers::pi / 2;

// oops, this prints pi/2, not pi/4 like you probably expected
// because old_angle isn't a float, it's a Property<float> that
// references v
std::cout << old_angle << '\n';

另请注意,这里有一个错误。考虑一下:

int main() {
    Vector2 v1;
    v1.x = 1;
    v1.y = 1;

    Vector2 v2 = v1;
    v2.angle = std::numbers::pi / 2;

    // Oops, assigning to v2.angle modified v1
    std::cout << "(" << v1.x << ", " << v1.y << ")\n";
}

您可以通过使

Property
不可复制来解决这些问题,但随后您会强制使用它的任何类实现自定义复制构造函数。另外,虽然这会使
auto
情况变得“安全”,但它是通过将其转变为编译错误来实现的。还是不太理想。


0
投票

我同意迈尔斯的观点。这不是最好的想法,因为这对于 C++ 开发人员来说是不自然的,并且您应该编写首先且最重要的是易于阅读的代码。

但是,作为一项工程挑战,这里有一个可能的实现:

#include <math.h>
#include <iostream>

template <typename T>
class Member
{
public:
    operator T() const { return _value; }
    void operator =(const T& value) const { _value = value; }                                                                                                                                                             void operator =(T&& value) { _value = std::move(value); }
                                                                                                                                                                                                                      private:
    T _value;
};

class Angle
{
public:
    Angle(const Member<float>& x, const Member<float>& y) :
        _x(x), _y(y) {}

    operator float() const { return atan2(_y, _x); }

private:
    const Member<float>& _x, _y;
};

class Obj
{
public:
    Member<float> x, y;
    Angle angle;

    Obj() : angle(this->x, this->y) {}
};

int main()
{
    Obj o;
    o.x = 3;
    o.y = 5;
    std::cout << o.x << ", " << o.y << " -> " << o.angle << std::endl;
}

0
投票

虽然其他解决方案似乎也是可能的,但这个似乎是最优雅的:P

using namespace std;

struct Vector2 {
    float x;
    float y;
    float init_length = [&]()-> float {return sqrt(x * x + y * y); }();
    float init_angle = [&]()-> float {return atan2(y, x); }();

    __declspec(property(get = GetAngle, put = SetAngle)) float angle;
    __declspec(property(get = GetLength, put = SetLength)) float length;


    Vector2() : x(0), y(0) {}
    Vector2(float a, float b) : x(a), y(b) {}
    ~Vector2() {}
    Vector2(Vector2& other) : x(other.x), y(other.y) {}
    Vector2(Vector2&& other) = delete;
    void operator =(Vector2&& other) = delete;

    void Display() {
        printf("%f\n%f\n%f\n%f\n\n", x, y, length, angle);
    }

    float GetLength() {
        return sqrt(x * x + y * y);
    }

    float GetAngle() {
        return atan2(y, x);
    }

    void SetLength(float l) {
        float a = GetAngle();
        x = cos(a) * l;
        y = sin(a) * l;
    }

    void SetAngle(float a) {
        float l = GetLength();
        x = cos(a) * l;
        y = sin(a) * l;
    }
};

int main()
{
    Vector2 vec = Vector2(10, 17);
    vec.Display();
    vec.length = 5;
    vec.Display();
}

0
投票

您可以使用

const
数据成员模拟
const&
get 函数:

class A {
    string data;
public:
    A( const string& iData ) : data( iData ) { }
    // Mimics a const property
    const string& Data = data;
};

用途:

A a( "Test" );

puts( a.Data.c_str() );
© www.soinside.com 2019 - 2024. All rights reserved.