一、背景
Microsoft c++ __declspec(property({get|put}))特殊属性 前面的文章我们已经介绍了基于Microsoft特殊属性,但是这个属性再linux gcc是编译不通过的,因为这个是依赖编译器的属性,C++语言层面并不支持该语法,所以要想实现跨平台,我们还需要另辟蹊径。
二、属性类
Microsoft c++ __declspec(property({get|put})本身就是一个语法糖,实现成员函数的调用就像成员变量一样方便,作为左值时调用set接口,作为右值时调用get结构,只不过编译器在编译时对左右值访问做了转码特殊处理,我们要是想c++实现相似功能,我们可以从左右值分开考虑。
(1) 左值
可以利用c++重载赋值运算符实现其他类型到对象的转换,当参数为对象相同类型时就是赋值构造函数。
(2) 右值
可以利用c++重载类型运算符,实现对象类型转换为其他类型。如 operator int();
根据上面左值右值的想法我们可以实现一个属性类,利用std::function实现绑定函数调用。
#include <iostream>
#include <functional>
//定义一个属性类
template<typename ValueType>
class Property
{
public:
using Getter = std::function<ValueType()>;
using Setter = std::function<void(const ValueType&)>;
Property(Getter&& get,Setter&& set)
:m_getter(std::move(get)),m_setter(std::move(set))
{
}
Property& operator=(const ValueType& src)
{
m_setter(src);
return *this;
}
operator ValueType() const
{
return m_getter();
}
private:
Property(const Property&) = delete;
Property& operator=(const Property&) = delete;
Getter m_getter;
Setter m_setter;
};
class Test
{
int m_data;
public:
void setData(const int& d)
{
if (d > 0 && d < 10000)
{
m_data = d;
}
}
int getData()
{
return m_data;
}
Property<int> data{ std::bind(&Test::getData,this),std::bind(&Test::setData,this,std::placeholders::_1) };
};
int main()
{
Test t;
t.data = 100;
std::cout << t.data << std::endl;
}
通过Propert构造函数绑定getter和setter方法,做类型转换时调用。这样实现了成员函数通过对象访问,但是我们这边定义的毕竟是类,可能会发生赋值和拷贝,Property又将赋值构造和拷贝构造指定为了privite,而且通过delete属性删除了这两个函数的实现,在实际应用中Property也是作为类的成员,所以我们只需要保证Test类可以赋值和拷贝。通过委托构造保证propert中getter和setter始终指向有效函数地址;
改造Test类
class Test
{
int m_data = 0;
public:
Test() :data(std::bind(&Test::getData, this), std::bind(&Test::setData, this, std::placeholders::_1))
{
}
Test(const Test&) :Test()
{
}
Test& operator=(const Test& src)
{
if (this == &src)
{
return *this;
}
m_data = src.m_data;
return *this;
}
void setData(const int& d)
{
if (d > 0 && d < 10000)
{
m_data = d;
}
}
int getData()
{
return m_data;
}
Property<int> data;
};
int main()
{
Test t;
t.data = 100;
std::cout << t.data << std::endl;
Test t2;
t2 = t1;
std::cout << t2.data << std::endl;
return 0;
}
这边只是简单了模拟了赋值构造,如果Test作为函数参数时,会发生拷贝,这个时候拷贝委托给普通构造保证了property中getter和setter的有效性。
三、运算
int main()
{
Test t;
t.data = 100;
std::cout << t.data << std::endl;
Test t2;
t2.data = t.data * 2 + 90;
std::cout << t2.data << std::endl;
return 0;
}
四、更多类型转换
在实际应用中我们可能需要不同类型可以隐式转换,这样我们处理不同类型时会大大方便,而不要再一个一个的调用转换函数。下面我们将Property支持一个int 到std::string的转换 ,这边我们可以偏特化一个property类来专门处理这个转换 而其他类型不受影响
template<typename ValueType,typename Enable = void>
class Property
{
public:
using Getter = std::function<ValueType()>;
using Setter = std::function<void(const ValueType&)>;
Property(Getter&& get, Setter&& set)
:m_getter(std::move(get)), m_setter(std::move(set))
{
}
Property& operator=(const ValueType& src)
{
m_setter(src);
return *this;
}
operator ValueType() const
{
return m_getter();
}
private:
Property(const Property&) = delete;
Property& operator=(const Property&) = delete;
Getter m_getter;
Setter m_setter;
};
template<typename ValueType>
class Property<ValueType,typename std::enable_if_t<std::is_integral_v<ValueType>>>
{
public:
using Getter = std::function<ValueType()>;
using Setter = std::function<void(const ValueType&)>;
Property(Getter&& get, Setter&& set)
:m_getter(std::move(get)), m_setter(std::move(set))
{
}
Property& operator=(const ValueType& src)
{
m_setter(src);
return *this;
}
operator ValueType() const
{
return m_getter();
}
Property& operator=(const std::string& src)
{
m_setter(std::atof(src.c_str()));
return *this;
}
operator std::string() const
{
return std::to_string(m_getter());
}
private:
Property(const Property&) = delete;
Property& operator=(const Property&) = delete;
Getter m_getter;
Setter m_setter;
};
int main()
{
Test t;
t.data = std::string("1000");
std::cout << t.data << std::endl;
Test t2;
t2.data = t.data * 2 + 90;
std::string result = t2.data ;
std::cout << result << std::endl;
return 0;
}
五、总结
虽然我们通过C++实现了Microsoft c++ __declspec(property({get|put}))特殊属性,但是我们本质上还是一个对象成员 ,如果在其他模板中使用时,需要特化property<>处理或者显示转换到实例化类型 如 (int)t.data;