10

我从 SO 上发布的几个问题中读到了这一段。

我不太明白为什么memcpy不能保证对于非 POD 类型是安全的。我的理解是这memcpy只是一个位明智的副本。

以下是标准的报价

POD对于type的任何对象(基类子对象除外)T,无论该对象是否拥有 type 的有效值T,构成该对象的底层字节 (1.7) 都可以复制到charunsigned 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
4

5 回答 5

17

尝试按位复制std::shared_ptr<>。您可能会发现您的程序经常在您的脸上炸毁。

对于复制构造函数执行按位复制以外的操作的任何类,您都会遇到此问题。在 的情况下std::shared_ptr<>,它将复制指针但不会增加引用计数,因此您最终会提前释放共享对象及其引用计数,然后在复制shared_ptr尝试减少释放的引用计数时爆炸.


更新:有人指出这并不能完全回答这个问题,这是公平的,因为我主要解决了将 shared_ptr 复制到 shared_ptr 的想法,而不是将 shared_ptr 复制到 char[] 并再次返回。但是,这个原则仍然成立。

如果按位将 shared_ptr 复制到 char[],为 shared_ptr 分配不同的值,然后将 char[] 复制回来,最终结果可能是泄漏一个对象并双重删除另一个对象,即 UB。

POD 也可能发生同样的情况,但这将是程序逻辑中的错误。只要程序理解并适应这样的事件,按位复制回修改后的 shared_ptr 的等效 POD 将是完全有效的。对 std::shared_ptr 这样做通常是行不通的。

于 2013-07-23T12:33:00.880 回答
6

想象一个类,它持有一些指向缓冲区的指针,如下所示:

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;
    }
} ;

如果您只是 memcopy 其构造的实例之一,您将获得两个指向同一个缓冲区的实例data,因为它们在data指针中具有相同的缓冲区地址。如果您在一个实例中修改数据,它也会在另一个实例中被修改。

这意味着您并没有真正将它克隆到两个独立的类中。此外,如果您随后删除这两个类,缓冲区将从内存中释放两次,这将导致崩溃。因此,该类必须定义一个复制构造函数,而您必须使用构造函数来复制它。

于 2013-07-23T12:31:23.833 回答
4

一般来说,问题在于对象不仅引入了数据,还引入了行为

通过手动复制数据,我们可能会破坏对象的固有行为,这可能依赖于复制构造函数。

一个很好的例子是任何共享的或唯一的指针——通过复制它,我们打破了我们在使用它时与该类达成的“交易”。

无论复制过程在语义上是否正确,这样做背后的想法都是错误的,并且违反了对象编程范式。

示例代码:

/** 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 错误)。

于 2013-07-23T12:39:05.020 回答
1

例如,假设您正在编写一个String类。该类的任何实例都应包含指向某个动态分配的char数组的指针。如果你 memcopy 这样一个实例,那么这两个指针将是相等的。对一个字符串的任何修改都会影响另一个字符串。

于 2013-07-23T12:33:25.543 回答
1

C++11 注意:问题中的引用是该规则的一个相当旧的版本。从 C++11 开始,该要求是可简单复制的,这比POD弱得多。


memcpy可以从任何对象使用。您将获得对象的按位图像。

如果对象不是 POD,则不能将图像用作与原始对象相同类型的图像,因为生命周期规则需要先完成初始化。

在这种情况下,图像只是一堆字节。这可能仍然有用,例如检测对象内部表示随时间的变化,但只有对字节有效的操作(例如两个图像之间的比较)是合法的,而不是需要原始类型对象的操作。

于 2013-07-23T13:43:38.627 回答