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

Pass callable object by value, assign it to pointer member

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

class Callable
{
public:
    void operator()(int x)
    {
        printf("x = %d\n", 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;
}

我确保 Callable 对象在我调用 UsesTheCallable::call() 之前超出范围,所以我 应该 调用内存中的函数那时实际上并不拥有。但是代码有效,并且 Valgrind 没有报告内存错误,即使我将一些成员数据放入 Callable class 并使 operator() 函数作用于该成员数据。

我是否正确认为此代码是未定义的行为?根据 Callable 是否有成员数据(例如私有 int 变量或其他东西),这段代码的 "defined-ness" 有什么不同吗?

m_callable = &callable;

Am I correct that this code is undefined behavior?

是的,这是废话!t,因为你给出的理由。

But the code works

是的,嗯,这就是 UB 发生的事情......

and Valgrind reports no memory errors

…特别是当您正在操作的内存仍然“属于”您的进程时。 Valgrind 在这里没有什么可检测的;它不验证 C++ 范围,仅验证“物理” 内存访问。而且程序不会崩溃,因为还没有太多机会破坏 c.

曾经占用的内存

“物理”指的是 OS 及其内存管理,而不是 C++ 的抽象概念。它实际上可能是虚拟内存或其他什么。

是的,这是未定义的行为。在构造函数 callable 的右大括号被销毁后,你有一个悬空指针。

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

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

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

然后我们使用

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

然后你可以看到这个bad behavior。在那个 运行 中它输出 x = 772773112 这是不正确的。