C++ 使用模板访问任何类的私有成员变量

在c++标准中,模板的显式实例化定义忽略成员访问说明符,因此可以利用这一特性使用模板来实现访问其他任何类的私有成员变量
参考:
https://en.cppreference.com/w/cpp/language/class_template
https://stackoverflow.com/questions/424104/can-i-access-private-members-from-outside-the-class-without-using-friends
https://github.com/lackhole/Lupin

实现模板代码

#include <utility>

namespace Access {

/// Explicit instantiation definitions ignore member access specifiers:
/// parameter types and return types may be private

template<typename Tag, auto Member>
struct Accessor;

template<typename Tag, typename _Cls, typename T, T _Cls::*Member>
struct Accessor<Tag, Member> {

    typedef T ValueType;

    const T &get(const _Cls &obj) {
        return obj.*Member;
    }

    void set(_Cls &obj, T value) {
        obj.*Member = std::move(value);
    }

    friend auto getAccessorTypeImpl(Tag) { return Accessor{}; }
};

template<typename Tag>
struct TagBase {
    friend auto getAccessorTypeImpl(Tag);
};

template<typename Accessor>
struct AccessorTrait {
    using AccessorType = Accessor;
    using ValueType = typename Accessor::ValueType;
};

template<typename Tag>
struct TagTrait {
    using TagType = Tag;
    using AccessorType = decltype(getAccessorTypeImpl(std::declval<TagType>()));
    using ValueType = typename AccessorTrait<AccessorType>::ValueType;
};

template<typename Tag, typename _Cls, typename Accessor = typename TagTrait<Tag>::AccessorType>
auto get(const _Cls &obj) -> const typename AccessorTrait<Accessor>::ValueType & {
    return Accessor{}.get(obj);
}

template<typename Tag,
         typename _Cls,
         typename Accessor = typename TagTrait<Tag>::AccessorType,
         typename T>
void set(_Cls &obj, T &&value) {
    /// typename AccessorTrait<Accessor>::ValueType
    return Accessor{}.set(obj, std::forward<T>(value));
}

}

使用如下

#include <cassert>
class AnyObject {
    int m_Data;
public:
    AnyObject() : m_Data(42) {}

    void assertValue(int v) {
        assert(v == m_Data);
    }
};

struct AnyObject_m_Data_Tag : Access::TagBase<AnyObject_m_Data_Tag> {};
template struct Access::Accessor<AnyObject_m_Data_Tag, &AnyObject::m_Data>; /// 显式实例化

int main() {
    AnyObject object;
    /// get
    int m_Data = Access::get<AnyObject_m_Data_Tag>(object);
    object.assertValue(m_Data);
    /// set
    Access::set<AnyObject_m_Data_Tag>(object, 24);
    object.assertValue(24);
	return 0;
}

在上面的代码中,先声明一个Tag用来标识某类中的私有成员,再用template struct Access::Accessor<AnyObject_m_Data_Tag, &AnyObject::m_Data>; 显示实例化Accessor,即访问者的实现,此时传入私有成员变量指针&AnyObject::M_Data是不会有编译错误的。
接下来就可以通过Tag来获取这个特化Accessor类的实例,进行私有成员变量的访问。
其中Tag与Accessor的关联可以通过全局的友元函数来实现

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《面向对象程序设计》试题 一、单选题(每空2分,共40分) 1、关于C++C语言关系的描述中,( )是错误的。 A.C语言C++语言的一个子集 B.C语言C++语言是兼容的 C.C++语言C语言进行了一些改进 D.C++语言C语言都是面向对象的 2、已知:int m=10; 下列表示引用的方法中,( )是正确的。 A.int& x=m; B.int& y=10; C.int& z; D.float& t=&m; 3、考虑下面的函数原型声明: void DefPar(int a, int b=7, char z = '*'); 下面函数调用中,不合法的是( )。 A.DefPar(5); B.DefPar(5,8); C.DefPar(5,'#'); D.DefPar(0,0,'*'); 4、系统在调用重载函数时往往根据一些条件确定哪个重载函数被调用,在下列选项中,不能作为依据的是( )。 A.函数的返回值型 B.参数的型 C.函数名称 D.参数个数 5、下列有关C++的说法中,不正确的是( )。 A.是一种用户自定义的数据型 B.只有中的成员函数或的友元函数才能存取中的私有成员 C.在中,如果不做特别说明,所有成员的访问权限均为私有的 D.在中,如果不做特别说明,所有成员的访问权限均为公用的 6、已知X,则当程序执行到语句X array[3];时,调用了( )次构造函数。 A.0 B.1 C.2 D.3 7、有关析构函数的说法,不正确的是( )。 A.析构函数有且仅有一个 B.析构函数和构造函数一样可以有形参 C.析构函数的功能是在系统释放对象之前作一些内存清理工作 D.析构函数无任何函数型 8、定义的内容允许被其对象无限制地存取的是( )。 A.private 部分 B. protected 部分 C.public 部分 D.以上都不对 9、关于常数据成员的说法,不正确的是( )。 A.常数据成员的定义形式与一般常变量的,只不过常数据成员的定义必须出现在体中 B.常数据成员必须进行初始化,并且不能被更新 C.常数据成员通过构造函数的成员初始化列表进行初始化 D.常数据成员可以在定义时直接初始化 10、运用运算符delete删除一个动态对象时( )。 A.系统首先为该动态对象调用构造函数,再释放其占用的内存 B.系统首先释放该动态对象占用的内存,再为其调用构造函数 C.系统首先为该动态对象调用析构函数,再释放其占用的内存 D.系统首先释放动态对象占用的内存,再为其调用析构函数 11、可在外用p.a的形式访问派生对象 p的基成员a,其中a是( )。 A.私有继承的公用成员 B.公用继承的私有成员 C.公用继承的保护成员 D.公用继承的公用成员 12、在公用继承方式下,有关派生对象和基对象的关系,不正确的叙述是(  )。 A.派生的对象可以赋给基的对象 B.派生的对象可以初始化基的引用 C.派生的对象可以直接访问中的成员 D.派生的对象的地址可以赋给指向基的指针 13、设置虚基的目的是( )。 A.简化程序 B.消除二义性 C.提高运行效率 D.减少目标代码 14、在C++中,用于实现动态多态性的是( )。 A.内联函数 B.重载函数 C.模板函数 D.虚函数 15、不能说明为虚函数的是( )。 A.析构函数 B.构造函数 C.的成员函数 D.以上都不对 16、如果一个至少有一个纯虚函数,那么就称该为( )。 A.抽象 B.派生 C.纯基 D.以上都不对 17、下面关于友元的描述中,错误的是( )。 A.友元函数可以访问私有数据成员 B.一个的友元中的成员函数都是这个的友元函数 C.友元可以提高程序的运行效率 D.之间的友元关系可以继承 18、下列运算符中,( )运算符在C++中不能被重载。 A.&& B.[ ] C.:: D.new 19、模板使用实际上是将模板实例化成一个( )。 A.函数 B.对象 C. D.抽象 20、假定MyClass为一个,则该的拷贝构造函数的声明语句为( )。 A.MyClass(MyClass x) B.MyClass&(MyClass x) C.MyClass(MyClass &x) D.MyClass(MyClass *x) 二、填空题(前16个空,每空1分,后2个空,每空2分,共20分) 1、和对象的关系可表述为:是对象的 ,而对象则是的 。 2、在C++中,三种继承方式的说明符号为 、 和 ,如果不加说明,则默认的继承方式为 。 3、如果只想保留公共基的一个复制,就必须使用关键字 把这个公共基声明为虚基。 4、若要把void fun( )定义为A的友元函数,则应在A的定义中加入语句 。 5、的静态成员分为 和 。 6、运算符重载要求保持其原来的操作数个数、 、 和语法结构。 7、通过关键字 可以声明模板,通过关键字 指定函数模板型参数,有几个型参数就有几个型关键字。 8、列出C++中两种用户自定义的数据型: 、 。 9、构造函数的作用是 。 10、后置自增运算符“++”重载为的成员函数(设名为A)的形式为 。 三、阅读下面3个程序,写出程序运行时输出的结果:(共13分) 1、#include <iostream> using namespace std; void fun(int &a,int &b) { int p; p=a; a=b; b=p; } void exchange(int &a,int &b,int &c) { if(a<b) fun(a,b); if(a<c) fun(a,c); if(b<c) fun(b,c); } void main() { int a=12,b=89,c=56; exchange(a,b,c); cout<<"a="<<a<<",b="<<b<< ",c="<<c<<endl; } 2、#include <iostream> using namespace std; class Date { public: Date(int,int,int); Date(int,int); Date(int); Date(); void display(); private: int month, day, year; }; Date::Date(int m, int d, int y) : month(m),day(d),year(y) { } Date::Date(int m,int d):month(m),day(d) { year=2009; } Date::Date(int m) : month(m) { day=1; year=2010; } Date::Date() { month=1; day=1; year=2010; } void Date::display() { cout <<month<<"/"<<day<<"/"<<year<<endl; } void main() { Date d1(12,31,2009); Date d2(12,31); Date d3(1); Date d4; d1.display(); d2.display(); d3.display(); d4.display(); } 3、#include <iostream> using namespace std; class A { public: A() { cout<<"constructing A "<<endl; } ~A() { cout<<"destructing A "<<endl; } }; class B: public A { public: B() { cout<<"constructing B "<<endl; } ~B() { cout<<"destructing B "<<endl; } }; class C : public B { public: C() { cout<<"constructing C "<<endl; } ~C() { cout<<"destructing C "<<endl; } }; void main() { C c1; } 四、编程题(共27分) 1、(10分)已知复数Complex的声明如下: class Complex { public: Complex(); Complex(double); Complex(double, double); friend Complex operator + (Complex&, Complex&); friend ostream & operator << (ostream&, Complex&); friend istream& operator >> (istream&, Complex&); private: double real, imag; }; 要求: (1)写出该的所有构造函数的外定义代码。 (2)写出对运算符“+”、“<<”、“>>”进行重载的运算符重载函数的定义。 2、(17分)下列Base是一个表示形状的抽象,area( )为求图形面积的函数,total( )则是一个通用的用以求不同形状的图形面积总和的函数。 class Base { public: virtual double area()=0; }; double total(Base *s[ ], int n) { double sum=0.0; for(int i=0; i<n; i++) sum+=s[i]->area( ); return sum; } 要求: (1)从Base派生圆(Circle)、正方形(Square),圆新增数据成员半径(radius),正方形新增数据成员边长(a),圆和正方形都有构造函数,修改、显示数据成员值的函数,求面积函数。 (2)写出main( )函数,计算半径为5.5的圆和边长为9.9的正方形的面积和(必须通过调用total函数计算)。
鸡啄米的资料,大家可以上官网看。这是我整理的压缩包,大家可以离线看。(这个资源还要大家积分,实在是惭愧,只因积分不够用,抱歉,体谅!) 大家要是觉得不错,可以下载我整理得另一套资源:VS2010+MFC编程入门,也是鸡啄米的,写得不错,通俗易懂! --------------------------完整目录如下------------------- 第一部分:C++编程概述 鸡啄米:C++编程入门系列之前言 鸡啄米:C++编程入门系列之一(进制数) 鸡啄米:C++编程入门系列之二(原码、反码与补码) 第二部分:C++简单程序设计 鸡啄米:C++编程入门系列之三(VS2010的使用介绍) 鸡啄米:C++编程入门系列之四(数据型) 鸡啄米:C++编程入门系列之五(运算符和表达式) 鸡啄米:C++编程入门系列之六(算法的基本控制结构之选择结构) 鸡啄米:C++编程入门系列之七(算法的基本控制结构之循环结构) 鸡啄米:C++编程入门系列之八(自定义数据型) 第三部分:函数 鸡啄米:C++编程入门系列之九(函数的定义与调用) 鸡啄米:C++编程入门系列之十(函数的参数传递与内联函数) 鸡啄米:C++编程入门系列之十一(重载函数与函数模板) 第四部分:与对象 鸡啄米:C++编程入门系列之十二(与对象:面向对象设计的基本思 想和特点) 鸡啄米:C++编程入门系列之十三(与对象:的声明、成员的访问 控制和对象) 鸡啄米:C++编程入门系列之十四(与对象:构造函数和析构函数) 鸡啄米:C++编程入门系列之十五(与对象:的组合) 鸡啄米:C++编程入门系列之十六(与对象:模板) 鸡啄米:C++编程入门系列之十七(与对象:UML简介) 第五部分:C++程序设计必知 鸡啄米:C++编程入门系列之十八(C++程序设计必知:作用域和可见 性) 鸡啄米:C++编程入门系列之十九(C++程序设计必知:生存期) 鸡啄米:C++编程入门系列之二十(C++程序设计必知:数据和函数) 鸡啄米:C++编程入门系列之二十一(C++程序设计必知:的静态成 员) 鸡啄米:C++编程入门系列之二十二(C++程序设计必知:友元) 鸡啄米:C++编程入门系列之二十三(C++程序设计必知:常引用、常对 象和对象的常成员) 鸡啄米:C++编程入门系列之二十四(C++程序设计必知:多文件结构和 编译预处理命令) 第六部分:数组、指针和字符串 鸡啄米:C++编程入门系列之二十五(数组、指针和字符串:数组的声 明和使用) 鸡啄米:C++编程入门系列之二十六(数组、指针和字符串:数组的存 储与初始化、对象数组、数组作为函数参数) 鸡啄米:C++编程入门系列之二十七(数组、指针和字符串:指针变量 的声明、地址相关运算--“*”和“&”) 鸡啄米:C++编程入门系列之二十八(数组、指针和字符串:指针的赋 值和指针运算) 鸡啄米:C++编程入门系列之二十九(数组、指针和字符串:指向数组 元素的指针和指针数组) 鸡啄米:C++编程入门系列之三十(数组、指针和字符串:指针用作函 数参数、指针型函数和函数指针) 鸡啄米:C++编程入门系列之三十一(数组、指针和字符串:对象指 针) 鸡啄米:C++编程入门系列之三十二(数组、指针和字符串:动态内存 分配和释放) 鸡啄米:C++编程入门系列之三十三(数组、指针和字符串:用字符数 组存放和处理字符串) 鸡啄米:C++编程入门系列之三十四(数组、指针和字符串:string ) 第七部分:继承与派生 鸡啄米:C++编程入门系列之三十五(继承与派生:概念介绍与派生 的声明) 鸡啄米:C++编程入门系列之三十六(继承与派生:派生从基继承 的过程) 鸡啄米:C++编程入门系列之三十七(继承与派生:派生对基成员 的访问控制之公有继承) 鸡啄米:C++编程入门系列之三十八(继承与派生:派生对基成员 的访问控制之保护继承与私有继承) 鸡啄米:C++编程入门系列之三十九(继承与派生:派生的构造函 数) 鸡啄米:C++编程入门系列之四十(继承与派生:派生的析构函数) 鸡啄米:C++编程入门系列之四十一(继承与派生:作用域分辨符) 鸡啄米:C++编程入门系列之四十二(继承与派生:虚基及其派生 的构造函数) 鸡啄米:C++编程入门系列之四十三(继承与派生:赋值兼容规则) 第八部分:多态性 鸡啄米:C++编程入门系列之四十四(多态性:多态的概念和型) 鸡啄米:C++编程入门系列之四十五(多态性:运算符重载的概念和规 则) 鸡啄米:C++编程入门系列之四十六(多态性:运算符重载为的成员 函数) 鸡啄米:C++编程入门系列之四十七(多态性:运算符重载为的友元 函数) 鸡啄米:C++编程入门系列之四十八(多态性:虚函数) 鸡啄米:C++编程入门系列之四十九(多态性:纯虚函数和抽象) 第九部分:异常处理 鸡啄米:C++编程入门系列之五十(异常处理) 鸡啄米:C++编程入门系列之目录和总结
Effective C++(编程的50个细节) Effective C++(编程的50个细节)着重讲解了编写C++程序应该注意的50个细节问题,书中的每一条准则描述了一个编写出更好的C++的方式,每一个条款的背后都有具体范例支持,书中讲的都是C++的编程技巧和注意事项,很多都是自己平时不太注意但又很重要的内容,绝对经典,作者Scott Meyers是全世界最知名的C++软件开发专家之一。 电子书PDF格式下载:http://www.yanyulin.info/pages/2013/11/effective.html 1、从C转向C++ 条款1:尽量用CONST和INLINE而不用#DEFINE 条款2:尽量用而不用 条款3:尽量用NEW和DELETE而不用MALLOC和FREE 条款4:尽量使用C++风格的注释 2、内存管理 条款5:对应的NEW和DELETE要采用相同的形式 条款6:析构函数里对指针成员调用DELETE 条款7:预先准备好内存不够的情况 条款8:写OPERATOR NEW与OPERATOR DELETE要遵循常规 条款9:避免隐藏标准形式的NEW 条款10:如果写了OPERATOR NEW就要同时写OPERATOR DELETE 条款11:为需要动态分配内存的声明一个拷贝构造函数和一个赋值函数 条款12:尽量使用初始化而不要在构造函数里赋值 条款13:初始化列表中成员列出顺序和它们在中的声明顺序相同 条款14:确定基有虚析构函数 条款15:让OPERATOR=返回*THIS的引用 条款16:在OPERATOR=中对所有数据成员赋值 条款17:在OPERATOR=中检查给自已赋值的情况 3、和函数:设计与声明 条款18:争取使的接口完整并且最小 条款19:分清成员函数,非成员函数和友元函数 条款20:避免PUBLIC接口出现数据成员 条款21:尽可能使用CONST 条款22:尽量用传引用而不用传值 条款23:必须返回一个对象时不要试图返回一个引用 条款24:在函数重载与设定参数默认值间慎重选择 条款25:避免对指针与数字型的重载 条款26:当心潜在的二义性 条款27:如果不想使用隐式生成的函数要显示的禁止它 条款28:划分全局名字空间 4、和函数:实现 条款29:避免返回内部数据的句柄 条款30:避免这样的成员函数,其返回值是指向成员的非CONST指针或引用 条款31:千万不要返回局部对象的引用,也不要返回函数内部用NEW初始化的指针 条款32:尽可能推迟变量的定义 条款33:明智的使用INLINE 条款34:将文件间的编译依赖性阡至最低 5、继承与面向对象设计 条款35:使公有继承体现是一个的函义 条款36:区分接口继承与实现继承 条款37:绝不要重新定义继承而来的非虚函数 条款38:绝不要重新定义继承而来的缺省参数值 条款39:避免向下转换继承层次 条款40:通过分层来体现有一个和用...来实现 条款41:区分继承和模板 条款42:明智的使用私有继承 条款43:明智的使用多继承 条款44:说你想说的,理解你说的 6、杂项 条款45:弄清C++在幕后为你所写、所调用的函数 条款46:宁可编译与链接时出错,也不要运行时出错 条款47:确保非局部静态对象在使用前被初始化 条款48:重视编译器警告 条款49:熟悉标准库 条款50:提高对C++的认识

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值