You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

析构函数误删元素:Texture实例#5被意外删除的技术问题

Fixing OpenGL Texture Double-Free Issue in Your C++ Code

Hey there, let's break down exactly what's causing your texture ID #5 to get deleted unexpectedly, and walk through the solutions to fix it.

The Root Problem: Shallow Copy + Resource Double-Free

Your Texture class manages an OpenGL texture resource (via GLuint m_texture), but you haven't defined copy constructors or copy assignment operators. C++ automatically generates default versions of these, which do a shallow copy—they just copy the value of m_texture (the texture ID) between objects, not create a new OpenGL texture or track how many objects are using the same ID.

Here's what happens in your failing case:

  1. You create a temporary Texture(0, 102, 153) object, which generates texture ID #5.
  2. You pass this temporary to SetTexture(Texture texture)—since it's pass-by-value, a copy of the temporary is made (both temp and parameter hold ID #5).
  3. m_texture = texture does another shallow copy, so now m_texture also holds ID #5.
  4. The function ends: the parameter texture is destroyed, calling ~Texture() which deletes ID #5 via glDeleteTextures.
  5. Now m_texture holds a stale, deleted texture ID—any use of it will fail, and when m_texture is destroyed later, it will try to delete ID #5 again (undefined behavior).

The "working" case with a named Texture texture(...) only seems to work because the named object is still alive after SetTexture returns—ID #5 isn't deleted until that named object is destroyed, but you still have a hidden double-free waiting for when both m_texture and the named object are destroyed.

Solutions to Fix This

Option 1: Disable Copying (If You Don't Need Shared Textures)

If your Texture objects shouldn't be copied at all, explicitly disable copy operations to prevent accidental shallow copies:

class Texture {
public:
    Texture(const std::string& fileName);
    Texture(int red, int green, int blue);
    void Bind();
    virtual ~Texture();

    // Disable copy constructor and assignment
    Texture(const Texture&) = delete;
    Texture& operator=(const Texture&) = delete;

private:
    void Init(unsigned char* imageData, int width, int height);
    GLuint m_texture;
};

Then update SetTexture to take a reference or move semantics:

// Pass by const reference (no copy made)
inline void SetTexture(const Texture& texture) {
    // Adjust your design to hold a reference/pointer (ensure texture outlives m_texture)
}

// Or use move semantics (transfer ownership of the texture)
inline void SetTexture(Texture&& texture) {
    m_texture = std::move(texture);
}

Option 2: Implement Reference Counting (For Shared Textures)

If you need multiple Texture objects to share the same OpenGL texture, add a reference counter to track how many objects are using the texture ID. Only delete the texture when the last object is destroyed.

Update your Texture class:

#include <memory> // For std::shared_ptr

class Texture {
public:
    Texture(const std::string& fileName);
    Texture(int red, int green, int blue);
    Texture(const Texture& other);
    Texture& operator=(const Texture& other);
    void Bind();
    virtual ~Texture();

private:
    void Init(unsigned char* imageData, int width, int height);
    GLuint m_texture;
    std::shared_ptr<int> m_refCount; // Tracks number of owners
};

Implement the copy operations and update constructors/destructor:

// Constructor initialization
Texture::Texture(const std::string& fileName) : m_refCount(std::make_shared<int>(1)) {
    int width, height, numComponents;
    unsigned char* imageData = stbi_load((fileName).c_str(), &width, &height, &numComponents, 4);
    if (imageData == NULL)
        std::cerr << "Texture loading failed for texture: " << fileName << std::endl;
    Init(imageData, width, height);
    stbi_image_free(imageData);
}

Texture::Texture(int red, int green, int blue) : m_refCount(std::make_shared<int>(1)) {
    unsigned char imageData[] = { static_cast<unsigned char>(red), static_cast<unsigned char>(green), static_cast<unsigned char>(blue) };
    Init(imageData, 1, 1);
}

// Copy constructor
Texture::Texture(const Texture& other) 
    : m_texture(other.m_texture), m_refCount(other.m_refCount) {
    (*m_refCount)++; // Increment reference count
}

// Copy assignment operator
Texture& Texture::operator=(const Texture& other) {
    if (this != &other) {
        // Decrement count for current texture; delete if no owners left
        (*m_refCount)--;
        if (*m_refCount == 0) {
            glDeleteTextures(1, &m_texture);
        }
        // Take ownership of the other texture's ID and reference count
        m_texture = other.m_texture;
        m_refCount = other.m_refCount;
        (*m_refCount)++;
    }
    return *this;
}

// Destructor
Texture::~Texture() {
    (*m_refCount)--;
    if (*m_refCount == 0) {
        glDeleteTextures(1, &m_texture);
    }
}

Option 3: Use Move Semantics (Transfer Texture Ownership)

If you want to transfer ownership of a texture from one object to another (instead of sharing), implement move constructors and move assignment operators. This lets temporary objects pass their texture ID to m_texture without copying, and the temporary object won't delete the texture.

Update your Texture class:

class Texture {
public:
    Texture(const std::string& fileName);
    Texture(int red, int green, int blue);
    Texture(Texture&& other) noexcept; // Move constructor
    Texture& operator=(Texture&& other) noexcept; // Move assignment
    void Bind();
    virtual ~Texture();

    // Disable copying if ownership transfer is your only use case
    Texture(const Texture&) = delete;
    Texture& operator=(const Texture&) = delete;

private:
    void Init(unsigned char* imageData, int width, int height);
    GLuint m_texture;
};

Implement move operations and update the destructor:

// Move constructor
Texture::Texture(Texture&& other) noexcept 
    : m_texture(other.m_texture) {
    other.m_texture = 0; // Reset source object so it doesn't delete the texture
}

// Move assignment operator
Texture& Texture::operator=(Texture&& other) noexcept {
    if (this != &other) {
        // Delete current texture if it exists
        if (m_texture != 0) {
            glDeleteTextures(1, &m_texture);
        }
        // Transfer ownership
        m_texture = other.m_texture;
        other.m_texture = 0;
    }
    return *this;
}

// Destructor: only delete if texture ID is valid
Texture::~Texture() {
    if (m_texture != 0) {
        glDeleteTextures(1, &m_texture);
    }
}

Now your original SetTexture will work correctly with temporary objects—std::move will transfer ownership from the temporary to m_texture:

inline void SetTexture(Texture texture) {
    m_texture = std::move(texture);
}

Final Recommendation

  • Use move semantics if you just need to pass textures around without sharing (most efficient, cleanest for temporary objects).
  • Use reference counting if you need multiple objects to share the same texture.
  • Use disable copying if textures should never be copied, and adjust your code to use references/pointers instead.

内容的提问来源于stack exchange,提问作者Freddy C.

火山引擎 最新活动