【问题标题】:Why a member function in a class template creates the same object in the same address为什么类模板中的成员函数在同一个地址创建同一个对象
【发布时间】:2018-07-07 01:12:58
【问题描述】:

我正在测试我最近编写的一个无锁工作窃取队列。但是,我发现了一个奇怪的问题,即当我将新项目推送到队列时,它只会返回最后推送的相同对象。我花了一天时间,我仍然无法弄清楚为什么会发生这种情况。 这是输出,每次推送到队列的数据都是一样的,这就是为什么我只有最后一个结果是 45,对于 4 个 res 项应该都是 45。任何人都可以在这里帮助我:(

  • push_back addr: 0x21d3ea0 bk: 0 &data: 0x7ffe36802380
  • push_back addr: 0x21d3ea4 bk: 0 &data: 0x7ffe36802380
  • push_back addr: 0x21d3ea8 bk: 0 &data: 0x7ffe36802380
  • push_back addr: 0x21d3eac bk: 0 &data: 0x7ffe36802380
  • res[0]=-1
  • res[1]=-1
  • res[2]=-1
  • res[3]=45

下面是简化代码:

#include <memory>
#include <functional>
#include <type_traits>

class FunctionWrapper {
private:
    class ImplInterface {
    public:
        virtual void invoke() = 0;
        virtual ~ImplInterface() {}
    };

    std::unique_ptr<ImplInterface> impl;

    template <typename F, typename... Args>
    class Impl : public ImplInterface {
    private:
        std::function<void()> callBack;
    public:
        Impl(F&& f_, Args&&... args_) {
            callBack = [&f_, &args_...]() { f_(std::forward<Args>(args_)...); };
        }

        void invoke() override { callBack(); } 
    };

public: 
    template <typename F, typename... Args>
    FunctionWrapper(F&& f_, Args&&... args_) : impl(new Impl<F, Args...>(std::forward<F>(f_), std::forward<Args>(args_)...)) {}

    void operator()() { impl->invoke(); }

    FunctionWrapper() = default;
    FunctionWrapper(FunctionWrapper&& other) : impl(std::move(other.impl)) {}
    FunctionWrapper& operator=(FunctionWrapper&& other) {
        impl = std::move(other.impl);
        return *this;
    }

    FunctionWrapper(const FunctionWrapper&) = delete;
    FunctionWrapper(FunctionWrapper&) = delete;
    FunctionWrapper& operator=(const FunctionWrapper&) = delete;
};
#include <atomic>
#include <array>

#include <iostream>

#include "functionwrapper.h"

class LockFreeWorkStealingQueue {
private:
    using DataType = FunctionWrapper;
    static constexpr auto DEFAULT_COUNT = 2048u;
    static constexpr auto MASK = DEFAULT_COUNT - 1u;
    std::array<DataType, DEFAULT_COUNT> q;
    unsigned int ft{0};
    unsigned int bk{0};
public:
    LockFreeWorkStealingQueue() {}
    LockFreeWorkStealingQueue(const LockFreeWorkStealingQueue&) = delete;
    LockFreeWorkStealingQueue& operator=(const LockFreeWorkStealingQueue&) = delete;

    void push_back(DataType data) {
        std::cout << "bk: " << (bk&MASK) << " &data: " << &data << std::endl;
        q[bk & MASK] = std::move(data);
        bk++;
    }

    bool try_pop_back(DataType& res) {
        if (bk > ft) {
            res = std::move(q[(bk - 1) & MASK]);
            bk--;
            return true;
        }
        return false;
    }

};
#include "lockfreeworkstealingqueue.h"
#include <iostream>
#include <algorithm>
#include <numeric>
#include <cassert>
#include <vector>

constexpr unsigned int NUM = 4;

void sumOver(const std::vector<int>& v, int& res) {
    res = std::accumulate(v.begin(), v.end(), 0);
    //std::cout << "call sumOver, res = " << res << std::endl;
    //std::cout << "call sumOver, addr: "  << &res << std::endl;
}

int main () {
    std::vector<int> v { 1,2,3,4,5,6,7,8,9 };
    std::vector<int> res(NUM, -1);
    std::vector<LockFreeWorkStealingQueue> wsq(4);

    {

        for (auto i = 0; i < 4; ++i) {
            for (auto j = 0; j < NUM / 4; ++j) {
                std::cout << "push_back addr: " << &res[i*(NUM/4)+j] << std::endl;
                wsq[i].push_back(FunctionWrapper(sumOver, std::ref(v), std::ref(res.at(i*(NUM/4)+j)))); 
            }
        }

        FunctionWrapper f;
        for (auto i = 0; i < 4; ++i) {
            for (auto j = 0; j < NUM / 4; ++j) {
                if(wsq[i].try_pop_back(f)) {
                    f();
                }
            }
        }
    } 

    for (auto i = 0; i < 4; ++i) {
        for (auto j = 0; j < NUM / 4; ++j) {
            std::cout << "res[" << i*(NUM/4)+j << "]=" << res[i*(NUM/4)+j] << std::endl;
        }
    }

    return 0;
}

编辑:我对 functionwrapper.h 进行了更改以反映 cmets。现在它运行良好。

#include <memory>
#include <functional>

class FunctionWrapper {
private:
    std::function<void()> callback;
public: 
    template <typename F, typename... Args>
    FunctionWrapper(F&& f_, Args&&... args_) : callback([f_, args_...]() { f_(args_...); }) {}

    void operator()() { callback(); }

    FunctionWrapper() = default;
    FunctionWrapper(FunctionWrapper&& other) : callback(std::move(other.callback)) {}
    FunctionWrapper& operator=(FunctionWrapper&& other) {
        callback = std::move(other.callback);
        return *this;
    }

    FunctionWrapper(const FunctionWrapper&) = delete;
    FunctionWrapper(FunctionWrapper&) = delete;
    FunctionWrapper& operator=(const FunctionWrapper&) = delete;
};

【问题讨论】:

  • Impl 通过引用捕获右值。这些引用很容易变得悬而未决。我还没有读到足够多的内容来查看显示的代码中是否确实发生了这种情况,但这是需要注意的事情。无论如何,您为什么要在类型擦除之上堆积类型擦除?为什么没有FunctionWrapper 直接有std::function 成员?
  • 这几乎不是minimal reproducible example。但是我看到您将指针推送到固定长度std::array 中的元素,那么您为什么不期望随着时间的推移重复使用相同的内存地址呢?此外,在您的输出消息中,&amp;data 是调用堆栈上的 局部变量 的地址,随着时间的推移肯定会被重用。输出q[bk &amp; MASK] 的地址而不是data 可能更有意义,因为这是您存储data 的地方。
  • 我不知道,如果我直接使用std::function,那么我无法通过将函数和所有参数推回队列来存储它,然后在我弹出时调用它出来。当我在其他部分声明 FunctionWrapper 时,我也不希望它作为模板。我不知道我怎么能以另一种方式做到这一点......@Igor Tandetnik
  • template &lt;typename F, typename... Args&gt; FunctionWrapper(F&amp;&amp; f_, Args&amp;&amp;... args_) : callback([f_, args_...]() { f_(args_...); } {}。我不太了解您所说的困难的性质。您无缘无故地将 Impl 设为类模板 - 它的成员 callback 实际上并不依赖于它的模板参数。
  • 声明复制构造函数和复制赋值操作符为已删除,类型为只移动。我不明白为什么这需要通过unique_ptr 持有callback。但如果你坚持:callback(std::make_unique&lt;std::function&lt;void()&gt;&gt;([f_, args_...]() { f_(args_...); }))

标签: c++11 templates c++14 move perfect-forwarding


【解决方案1】:

FunctionWrapper::Impl 中的 lambda 捕获对临时 std::reference_wrapper 实例的引用(由 std::ref 中的 main 调用产生)。到实际调用 lambda 时,这些临时对象早已被销毁,并且引用处于悬空状态。通过访问生命周期已结束的对象,您的程序会表现出未定义的行为。

您想改为按值捕获,如

Impl(F&& f_, Args&&... args_) {
  callBack = [f_, args_...]() { f_(args_...); };
}

Demo

【讨论】:

  • 非常感谢!!!那就是问题所在。啊,我总是告诉自己在使用 lambda 时要注意悬挂指针。还在犯错..
猜你喜欢
  • 2023-03-07
  • 2021-12-18
  • 1970-01-01
  • 1970-01-01
  • 2019-11-15
  • 2012-06-08
  • 1970-01-01
  • 1970-01-01
  • 2019-08-07
相关资源
最近更新 更多