我从SO上发布的几个问题中读到了这一段。
我无法弄清楚为什么{P}不保证对于非POD类型是安全的。memcpy
。我的理解是memcpy
只是一个有点明智的副本。
以下是标准
的引用对于
POD
类型T
的任何对象(基类子对象除外),无论对象是否包含T
类型的有效值, 组成对象的底层字节(1.7)可以复制到char
或unsigned char
的数组中.41)如果内容 将char
或unsigned char
数组复制回对象,该对象随后将保留其原始对象 值。# define N sizeof (T) char buf[N]; T obj ; // obj initialized to its original value std :: memcpy (buf , & obj , N); // between these two calls to std::memcpy, // obj might be modified std :: memcpy (& obj , buf , N); // at this point, each subobject of obj of // scalar type holds its original value
答案 0 :(得分:17)
尝试逐位复制std::shared_ptr<>
。您可能会发现您的程序经常在您的脸上爆炸。
对于任何其复制构造函数执行除逐位复制之外的操作的类,您将遇到此问题。在std::shared_ptr<>
的情况下,它将复制指针但不会增加引用计数,因此您最终会提前释放共享对象及其引用计数,然后在复制时{{{{{ 1}}尝试减少释放的引用计数。
更新:有人指出,这并没有完全回答这个问题,这是公平的,因为我主要解决了将shared_ptr复制到shared_ptr而不是shared_ptr复制到char []并返回的想法。再次。但是,原则仍然存在。
如果你将shared_ptr按位逐位复制到char [],则为shared_ptr指定一个不同的值,然后将char []复制回来,最终结果可能是泄漏一个对象并双重删除另一个对象,即,UB。
POD可能会发生同样的情况,但这可能是程序逻辑中的一个错误。只要程序理解并适应这样的事件,逐位复制回POD等效于修改后的shared_ptr就完全有效。对std :: shared_ptr执行此操作通常不起作用。
答案 1 :(得分:5)
想象一个类,它包含一些指向缓冲区的指针:
class Abc {
public:
int* data;
size_t n;
Abc(size_t n)
{
this->n = n;
data = new int[n];
}
// copy constructor:
Abc(const Abc& copy_from_me)
{
n = copy_from_me.n;
data = new int[n];
memcpy(data, copy_from_me.data, n*sizeof(int));
}
Abc& operator=(const Abc& copy_from_me)
{
n = copy_from_me.n;
data = new int[n];
memcpy(data, copy_from_me.data, n*sizeof(int));
return *this;
}
~Abc()
{
delete[] data;
}
} ;
如果只记忆其构建的实例之一,则会得到两个指向同一缓冲区data
的实例,因为它们在data
指针中具有相同的缓冲区地址。如果您在一个实例中修改数据,它也将在另一个实例中修改。
这意味着你没有真正将它克隆到两个独立的类中。此外,如果然后删除这两个类,缓冲区将从崩溃的内存中释放两次。 因此,类必须定义一个复制构造函数,您必须使用构造函数复制它。
答案 2 :(得分:3)
一般来说,问题是对象不仅会引入数据,还会引入行为。
通过手动复制数据,我们可能会破坏对象的固有行为,这可能依赖于复制构造函数。
一个很好的例子是任何共享或唯一指针 - 通过复制它我们打破了我们在使用它时对该类进行的“交易”。
无论复制过程在语义上是否正确,这背后的想法都是错误的,违反了对象编程范例。
示例代码:
/** a simple object wrapper around a pthread_mutex
*/
class PThreadMutex
{
public:
/** locks the mutex. Will block if mutex is already locked */
void lock();
/** unlocks the mutex. undefined behavior if mutex is unlocked */
void unlock();
private:
pthread_mutex_t m_mutex;
};
/** a simple implementation of scoped mutex lock. Acquires and locks a Mutex on creation,
* unlocks on destruction
*/
class ScopedLock
{
public:
/** constructor specifying the mutex object pointer to lock
* Locks immediately or blocks until lock is free and then locks
* @param mutex the mutex pointer to lock
*/
ScopedLock ( PThreadMutex* mutex );
/** default destructor. Unlocks the mutex */
~ScopedLock ();
/** locks the mutex. Will block if mutex is already locked */
void unlock();
private:
PThreadMutex* m_mutex;
// flag to determine whether the mutex is locked
bool m_locked;
// private copy constructor - disable copying
ScopedLock(ScopedLock &mutex) { (void)mutex; /* to get rid of warning */ };
};
如果复制ScopedLock
类,手动解锁,然后恢复该值并在构造函数中执行另一个解锁,则会导致未定义的行为(或者析构函数中至少出现EPERM错误)。
答案 3 :(得分:1)
假设您正在编写String
类。该类的任何实例都应该包含指向某个动态分配的char
数组的指针。如果你记住这样一个实例,那么两个指针将是相等的。对一个字符串的任何修改都会影响另一个字符串。
答案 4 :(得分:1)
C ++ 11注意:问题中的引用是规则的旧版本。从C ++ 11开始,要求是平易可复制的,它比 POD 弱得多。
memcpy
可以在任何对象中使用。你得到一个对象的按位图像。
如果对象不是POD,则不能使用图像,就好像它与原始对象的类型相同,因为生命周期规则需要首先完成初始化。
在这种情况下,图像只是一堆字节。这可能仍然有用,例如,检测对象的内部表示随时间的变化,但只有对字节有效的操作(例如两个图像之间的比较)是合法的,而不是需要原始类型的对象的操作。