SFML中使用vector::emplace_back添加带Font成员的TextButton触发段错误
问题原因与解决方案
核心问题分析
- SFML的
sf::Text调用setFont()时,不会复制字体数据,仅保存一个指向sf::Font对象的引用。 - 当用
emplace_back()添加第二个按钮时,std::vector因容量不足触发内存扩容:它会在新内存构造新对象,将旧内存中的TextButton移动到新位置后销毁旧对象。 - 移动后的
TextButton中,sf::Text仍指向旧内存中已销毁的sf::Font成员,调用window.draw(text)时访问悬空引用,触发段错误。 - 而
push_back()场景下,先创建独立对象再拷贝,每个拷贝的TextButton都有独立的sf::Font,sf::Text指向自身的font成员,无悬空问题;单个emplace_back时vector无需扩容,没有移动操作,因此正常运行。
解决方案
方案1:用智能指针共享字体(推荐,同时优化性能)
每个按钮重复加载字体冗余且低效,使用std::shared_ptr<sf::Font>让所有按钮共享同一字体实例,彻底解决引用悬空:
#include <memory> class TextButton { public: using Action = void(*)(); private: sf::Vector2f position; sf::Vector2f size; Action action; sf::RectangleShape shape; sf::Text text; std::shared_ptr<sf::Font> font; // 替换原sf::Font成员 public: // 保持原有成员函数声明不变 TextButton(const sf::Vector2f& position, const sf::Vector2f& size, const sf::Color& color, const unsigned char* binaryFont, const unsigned int binaryFontSizeInBytes, const int characterSize, const sf::Color& textColor, const std::string& label, Action action); void draw(sf::RenderWindow& window) const; bool is_clicked(const sf::Vector2f& mousePosition) const; void on_click() const; }; // 构造函数实现修改 TextButton::TextButton(const sf::Vector2f& position, const sf::Vector2f& size, const sf::Color& color, const unsigned char* binaryFont, const unsigned int binaryFontSizeInBytes, const int characterSize, const sf::Color& textColor, const std::string& label, Action action) : position(position), size(size), action(action), font(std::make_shared<sf::Font>()) { shape.setSize(size); shape.setPosition(position); shape.setFillColor(color); font->loadFromMemory(binaryFont, binaryFontSizeInBytes); text.setFont(*font); // 绑定到智能指针指向的字体 text.setString(label); text.setCharacterSize(characterSize); text.setFillColor(textColor); sf::FloatRect textBounds = text.getLocalBounds(); text.setOrigin(textBounds.left + textBounds.width/2.f, textBounds.top + textBounds.height/2.f); text.setPosition(position.x + shape.getSize().x/2.f, position.y + shape.getSize().y/2.f); } // 其余成员函数实现保持不变
方案2:手动实现拷贝/移动构造函数
若坚持每个按钮独立持有字体,需手动实现拷贝、移动构造及赋值运算符,确保sf::Text始终指向当前对象的font成员:
class TextButton { // 原有成员声明不变 public: // 拷贝构造 TextButton(const TextButton& other) : position(other.position), size(other.size), action(other.action), shape(other.shape), text(other.text), font(other.font) { text.setFont(font); // 重新绑定到当前对象的font } // 拷贝赋值 TextButton& operator=(const TextButton& other) { if (this != &other) { position = other.position; size = other.size; action = other.action; shape = other.shape; font = other.font; text = other.text; text.setFont(font); } return *this; } // 移动构造 TextButton(TextButton&& other) noexcept : position(std::move(other.position)), size(std::move(other.size)), action(std::move(other.action)), shape(std::move(other.shape)), text(std::move(other.text)), font(std::move(other.font)) { text.setFont(font); } // 移动赋值 TextButton& operator=(TextButton&& other) noexcept { if (this != &other) { position = std::move(other.position); size = std::move(other.size); action = std::move(other.action); shape = std::move(other.shape); font = std::move(other.font); text = std::move(other.text); text.setFont(font); } return *this; } // 原有成员函数声明不变 };
方案3:提前预留vector容量(临时规避)
提前给vector分配足够容量,避免扩容时的移动操作,但仅能临时解决问题,后续添加更多按钮仍可能触发错误:
std::vector<TextButton> buttons; buttons.reserve(2); // 提前预留2个元素的容量 buttons.emplace_back(sf::Vector2f(200.f, 200.f), sf::Vector2f(300.f, 80.f), sf::Color::Green, low_bit_pixel_ttf, low_bit_pixel_ttf_len, 30, sf::Color::Black, "button 1", []{ std::cout << "first button pressed\n"; }); buttons.emplace_back(sf::Vector2f(500.f, 350.f), sf::Vector2f(200.f, 60.f), sf::Color::Red, low_bit_pixel_ttf, low_bit_pixel_ttf_len, 30, sf::Color::White, "button 2", []{ std::cout << "second button pressed\n"; });
内容的提问来源于stack exchange,提问作者user21766581
相关产品推荐
相关产品推荐

