按值传递可调用对象,将其分配给指针成员

Pass callable object by value, assign it to pointer member

本文关键字:分配 指针 成员 调用 对象 按值传递      更新时间:2023-10-16

我们从一个分包商那里收到了代码,该分包商主要执行以下操作:

class Callable
{
public:
    void operator()(int x)
    {
        printf("x = %dn", x);
    }
};
template<typename T>
class UsesTheCallable
{
public:
    UsesTheCallable(T callable) :
            m_callable(NULL)
    {
        m_callable = &callable;
    }
    ~UsesTheCallable() {}
    void call() { (*m_callable)(5); }
private:
    T* m_callable;
};

这让我觉得是未定义的代码。。。他们按值将T传递给UsesTheCallable构造函数,然后将m_callable成员分配给参数的地址,该地址在构造函数末尾应该超出范围,因此每当我调用UsesTheCallable::call()时,我都会对一个不再存在的对象执行操作。

所以我尝试了这个主要方法:

int main(int, char**)
{
    UsesTheCallable<Callable>* u = NULL;
    {
        Callable c;
        u = new UsesTheCallable<Callable>(c);
    }
    u->call();
    delete u;
    return 0;
}

在调用UsesTheCallable::call()之前,我会确保Callable对象超出范围,因此我应该调用内存上的函数,而此时我实际上并不拥有该函数。但代码是有效的,Valgrind没有报告内存错误,即使我将一些成员数据放入Callable类并使operator()函数对该成员数据进行操作也是如此。

我认为这个代码是未定义的行为,这是正确的吗?根据Callable是否具有成员数据(例如,私有int变量或其他什么),此代码的"定义性"是否存在差异?

是的,这是未定义的行为。构造函数callable的右大括号被破坏后,您就有了一个悬空指针。

您没有看到不良影响的原因是,在实例超出范围后,您确实没有使用实例。函数调用操作符是无状态的,因此它不会试图访问它不再拥有的内存。

如果我们像一样向可调用对象添加一些状态

class Callable
{
    int foo;
public:
    Callable (int foo = 20) : foo(foo) {}
    void operator()(int x)
    {
        printf("x = %dn", x*foo);
    }
};

然后我们使用

int main()
{
    UsesTheCallable<Callable>* u = NULL;
    {
        Callable c(50);
        u = new UsesTheCallable<Callable>(c);
    }
    u->call();
    delete u;
    return 0;
}

然后你就会看到这种恶劣的行为。在该运行中,它输出不正确的x = 772773112

m_callable = &callable;

我认为这个代码是未定义的行为,这是正确的吗?

是的,这太夸张了!t、 因为你给出的理由。

但是这个代码是

是的,好吧,UB就是这样…

Valgrind报告没有内存错误

……特别是当你正在操作的内存仍然";属于";您的流程。Valgrind在这里没有什么可探测的;它不验证C++作用域;物理"内存访问。该程序不会崩溃,因为还没有什么东西有机会破坏c曾经占用的内存。

";物理";我指的是操作系统及其内存管理,而不是C++的抽象概念。它实际上可能是虚拟内存或其他什么