std::具有指向数据成员的指针的线程

std::thread with pointer to data member

本文关键字:指针 线程 数据成员 std      更新时间:2023-10-16

我在cppreference上阅读了std::thread文档(我知道并不总是100%准确(,并注意到当传递"指向数据成员的指针"(而不是"指向成员函数的指针"(作为其第一个参数(f(和所需类的对象作为其第二个参数(复制到线程本地存储后的t1(时,std::thread的行为定义如下:

如果N==1并且f是指向类的成员数据对象的指针,则访问它。对象的值将被忽略。实际上,执行以下代码:t1.*f如果并且t1的类型是T、对T的引用或对从T导出的类型的引用。(*t1(.*f否则。

现在,我不打算以这种方式使用std::thread,但我对这个定义感到困惑。显然,唯一会发生的事情是访问数据成员并忽略该值,这似乎根本不会产生任何可观察到的副作用,这意味着(据我所知(这可能是一个否定。(我可能遗漏了一些明显的东西…?(

起初,我认为这可能是印刷错误,意思是说数据成员被访问,然后被调用(因为它可能是一个可调用的对象,即使它不是一个函数(,但我在GCC-4.7中用以下代码进行了测试,实际上没有调用:

#include <iostream>
#include <thread>
struct S 
{
    void f() {
        std::cout << "Calling f()" << std::endl;
    }
    struct {
        void operator()() {
            std::cout << "Calling g()" << std::endl;
        }
    } g;
};
int main(int, char**)
{
    S s;
    s.f(); // prints "Calling f()"
    s.g(); // prints "Calling g()"
    std::cout << "----" << std::endl;
    auto x = &S::f; // ptr-to-mem-func
    auto y = &S::g; // ptr-to-data-mem
    (s.*x)(); // prints "Calling f()"
    (s.*y)(); // prints "Calling g()"
    std::cout << "----" << std::endl;
    std::thread t(x, &s);
    t.join();
    // "Calling f()" printed by now
    std::thread u(y, &s);
    u.join();
    // "Calling g()" not printed
    return 0;
}

这个定义有没有什么目的似乎什么都没有?为什么不让传递"指向数据成员的指针可调用"就像传递"指向成员函数的指针"一样,并让传递"数据成员的指示器不可调用"成为一个错误呢?事实上,这似乎是实现它的最简单方法,因为调用"可调用的数据成员指针"与在其他上下文中调用"成员函数指针"具有等效的语法(除非模板专用化和SFINAE规则的变幻莫测使其难以同等对待…?(

这不是我实际代码所需要的东西,但这个定义的存在让我怀疑我错过了一些基本的东西,这让我很担心……有人能启发我吗?

这是因为C++11标准不仅定义了线程的启动方式,还定义了std::bindstd::function的工作方式。

事实上,C++11标准的第30.3.1.2/3段规定了类std::thread:的可变构造函数

template <class F, class ...Args> explicit thread(F&& f, Args&&... args);

效果:构造线程类型的对象。执行的新线程执行INVOKE (DECAY_- COPY ( std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...),并调用CCD_ 10在构造线程中被评估。此调用的任何返回值都是已忽略。[…]

忽略DECAY_COPY的作用(与问题无关(,这就是第20.8.2段定义INVOKE伪函数的方式:

定义INVOKE(f,t1,t2,…,tN(如下:

--(t1.*f((t2,…,tN(当f是指向类T的成员函数的指针并且t1是类型T或对类型T的对象的引用或对从T派生的类型的对象的参考;

--((*t1(.*f((t2,…,tN(当f是指向类T的成员函数的指针并且t1不是前一项中所述的类型;

--当N==1并且f是指向类T的成员数据的指针并且t1是类型T或a的对象时对类型为T的对象的引用或对从T派生的类型的对象的参考;

--(*t1(.*f当N==1并且f是指向类T的成员数据的指针并且t1不是类型之一时如前一项所述

--f(t1,t2,…,tN(。

现在的问题变成了:

为什么C++11标准会这样定义INVOKE功能?

答案是这里