按值传递可调用对象,将其分配给指针成员
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
这是不正确的。
我们从一个分包商那里收到了主要执行以下操作的代码:
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
这是不正确的。