被 C++的语法问题折磨了,有没有大佬帮我解答一下疑惑

2020-10-25 22:32:01 +08:00
 rainboat
class Test{
public:
    int a, b, c;
    Test(int i){
        a = i;
        b = c = 0;
    }
    ~Test(){
        a = b = c = 0;
    }
};

int Test::*v[] = {&Test::a, &Test::b, &Test::c};

void f(Test *a, int Test::*b, int c){
    a->*b = c;
}

int main(){
    // 输出结果为 1
    cout << &Test::c << endl;
}

考研刷题时碰到的一道题中的一部分代码,我知道 Test::是类的作用域,但是这个&Test::c是个什么东西,还有这个int Test::*v[] = {&Test::a, &Test::b, &Test::c}又是一个什么神奇的数组,翻了半天书也没看出个所以然,上网查也不知道用什么关键词,有没有懂的大佬能帮我一下。

5333 次点击
所在节点    C++
43 条回复
Akiyu
2020-10-26 11:40:54 +08:00
追加一些疑问和理解;

Test t;
f(&t, &Test::a, 10);
cout << "------------------" << endl;
cout << &Test::a << endl;
cout << "------------------" << endl

从上面代码汇编后的结果可以知 &Test::a 实际是 Test 中 a 的偏移.
其中 a 为 Test 首元素, 值为 0 (这点可以通过调用 f 函数时的第二个参数值获得).
但是为什么 &Test::a 打印后的值为 1 (值为 0 的话应该是 false. 或 0).
并且这个值在参数传递时固定为 1 (无论是 &Test::a, 还是 &Test::b, 或者 &Test::c. 甚至成员函数).

关于值为何为 1 我的理解是: 这些值实际的地址不可能是 0. 即使 &Test::a 为 0. 代表其偏移为 0. 但和实际的对象结合后, 其真实地址不可能为 0. 或许编译器对此做了优化.

后续:
我试过取成员函数(普通 /virtual, cv 限定, 我未定义但编译器自动生成的函数)地址. 都可以获取地址. 但其构造和析构无法获取. 关于构造为何无法获取. 我同意这个答案:
https://softwareengineering.stackexchange.com/questions/245613/why-doesnt-c-allow-you-to-take-the-address-of-a-constructor
但是关于析构为何无法获取. 我未得到满意的答案. 有人知道么?
by73
2020-10-26 12:05:55 +08:00
@Akiyu 看楼上有三张图的那个,你可以尝试 godbolt.org 去编译一下,会发现纯粹是编译器将 &Test::a 转换成了 bool 值。。
zxCoder
2020-10-26 12:20:47 +08:00
c++ 永远地神
代码最乱的一门语言
codehz
2020-10-26 12:23:48 +08:00
@Akiyu 别啥都以汇编为导向,c++这玩意实现定义的东西很多,如果按某个特定实现来学,很容易写出不可移植的东西出来(甚至有些编译器升级也会打破原有的假设)
打印为 1 的原因就是
1.编译器没有找到合适的 operator<<重载
2.然后发现存在一条隐式转换规则(空成员指针值变为 false ;所有其他值变为 true )
3.接着发现它不是空成员指针,就转换成 true
(注意,这里不是表示实现定义的值不为 0,实际上如果按照偏移量方式实现,那平凡结构体的首个元素就应该是 0,但是它不是空成员指针,所以仍然是 true)
4.标准约定默认布尔类型输出规则是 true -> 1 false -> 0,所以这里输出 1
Wirbelwind
2020-10-26 13:47:34 +08:00
我记得是 offset

深度探索 C++对象模型也讲过
QBugHunter
2020-10-26 13:59:01 +08:00
总觉得这是回的第四种写法。。。。。
QBugHunter
2020-10-26 14:11:36 +08:00
题目没太仔细看,我有几个问题
1.如果 Test 是个类,那成员变量为什么是 public 的?
2.如果 Test 作为一个结构,为什么不用 Test.a,Test->b 这种写法?
3.Test 的析构函数里的那行代码有什么意义,一个类的析构函数把成员变量的值设为 0 ?
codehz
2020-10-26 16:34:15 +08:00
@QBugHunter #27 有意义啊,就像函数指针有啥意义,为啥不用函数一样,通过指针可以实现间接访问,而计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决(
总所周知,c++暂时还没有编译期反射这种东西,没法按照名字来访问未知类型的成员,但是有了这个机制,你至少可以让用户传入成员的指针,然后在模板函数中实现对成员的操作。。。
这种事在 c 的世界也不是没有,那时候通常都是用偏移量( offsetof ),显然直接的偏移量在 c++的世界有诸多水土不和的问题(例如多继承的问题和类型安全的问题),自然要设计一个适合于 c++的"偏移量"解决方案。。。
QBugHunter
2020-10-26 16:36:40 +08:00
@codehz
你是回复我 3 个问题中的那个?
codehz
2020-10-26 16:46:52 +08:00
@QBugHunter #29 是回的第四种写法(
其他三个问题没啥好问的,问就是作者想这样做而已,毕竟目的不是为了可维护性什么的,就为了演示 /实验这个用法。。。
--
我猜测你可能把这个用法当作“绕过 private 访问”的用法了,才会有这三个问题(
Hconk
2020-10-26 16:50:35 +08:00
@QBugHunter
1. C++ 允许变量是 public 的,这里的变量就是用 public 修饰。
2. .和-> 是类的实例化对象使用的,类通过::访问成员。
3. 本身这段代码就没什么意义,只是用来演示语法的。。析构函数没什么意义。
QBugHunter
2020-10-26 17:01:56 +08:00
@codehz
不是。。。我的意思是这种语法是否有实际用途,也就是说将类的成员设为公有,直接操作类成员而不是通过接口,因为在我看来,公有类成员是非常危险的
v2Mark
2020-10-26 17:07:43 +08:00
有意思的题目,考察的还挺细节的
wutiantong
2020-10-26 17:30:53 +08:00
@QBugHunter 我倒觉得无脑上 getter setter 那才叫脱裤子放屁,多此一举。。。
user8341
2020-10-26 17:37:42 +08:00
编译的时候不知道要访问哪个数据成员,执行的时候才知道。
codehz
2020-10-26 17:52:56 +08:00
@QBugHunter #32
首先,c++的 class 和 struct 根本没有自带立场,只是某些编码规范强加的属性。。。
其次,直接访问成员的危险性在于破坏了潜在的维护性,但前提是它真的是需要隐藏的实现细节。。。
然后这个语法也不是不可以在类内部导出成员指针。。。
最后,这个语法也是另一个层面的“接口”,只不过抽象程度更高,而不具备一般意义上的形式而已。
相当于提供了一个特定上下文下,设置和读取特定值的方法。
然后它们都可以被模板化(上下文类由用户编译期指定,用成员函数来指定特定的成员),只要你认同模板的封装的功能,你就应该理解这种语法也有它存在的意义(结合上面的)。。。
no1xsyzy
2020-10-26 20:36:59 +08:00
@QBugHunter 如果不需要类似 ORM 反射之类的修改存取过程,那用 getter 和 setter 没什么意义,至于某天突然发现需要了,还可以一键重构。
另外,这个语法听起来也可以变成指向方法的指针,之后可以 obj->*methp(params) ,不知道有没有理解错……
ysc3839
2020-10-26 21:10:44 +08:00
@no1xsyzy
> 另外,这个语法听起来也可以变成指向方法的指针

应该是可以的,std::bind 就是类似的写法。
GeruzoniAnsasu
2020-10-26 21:58:38 +08:00
Tony042
2020-10-26 22:16:42 +08:00
@ysc3839 这个是 class member pointer 和 class member function pointer,是可以指向方法的,std::bind 和这个关系不太大,bind 对 function,function like,和 member function pointer 做了偏特化,我感觉用起来区别还是很大的,bind 主要是用来固定和调整某些 function parameter,class member function pointer 这东西就是 C++黑科技之一,有用到的时候,但用的情况不是很多

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/718467

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX