第五章,技术
25-将construct和non-member_functions虚化
“虚化” Construct
这里的虚化并不是说使用virtual修饰构造函数,而是更广义的虚化,意在实现多态构造。
class NLComponent { public: virtual NLComponent* clone() const = 0; };
class TextBlock : public NLComponent { public: virtual TextBlock * clone() const { return new TextBlock(*this); } ... };
class Graphic : public NLComponent { public: virtual Graphic * clone() const { return new Graphic(*this); } ... };
class NewsLetter { public: NewsLetter(const NewsLetter& rhs) { for (auto it : rhs.components) { components.push_back(it->clone()); } } ...
private: list<NLComponent*> components; };
|
“虚化” Non-member functions
将Non-member functions的行为“虚化”,其行为视其参数的动态类型而不同。
要实现这种效果,主要通过写一个虚函数做实际工作,然后再写一个非虚函数来调用这个虚函数。
class NLComponent { public: virtual ostream& print(ostream& s) const = 0; ... };
class TextBlock : public NLComponent { public: virtual ostream& print(ostream& s) const; ... };
class Graphic : public NLComponent { public: virtual ostream& print(ostream& s) const; ... };
inline ostream& operator<<(ostream& s, const NLComponent& c) { return c.print(s); }
|
完整代码用例
class NLComponent { public: virtual NLComponent* clone() const = 0; virtual ostream& print(ostream& s) const = 0; };
class TextBlock : public NLComponent { public: TextBlock(string path) : textPath(path) {} ~TextBlock() {}
virtual TextBlock * clone() const { return new TextBlock(*this); } ostream& print(ostream& s) const { return s << "TextBlock:\n" << textPath << "\n"; };
private: string textPath; };
class Graphic : public NLComponent { public: Graphic(string path) : imgPath(path) {} ~Graphic() {};
virtual Graphic * clone() const { return new Graphic(*this); }
ostream& print(ostream& s) const { return s << "Graphic:\n"<< imgPath << "\n"; };
private: string imgPath; };
class NewsLetter { public: NewsLetter() = default; NewsLetter(const NewsLetter& rhs) { for (const auto* comp : rhs.components) { components.push_back(comp->clone()); } }
~NewsLetter() { for (auto* comp : components) { delete comp; } } void addComponent(NLComponent* comp) { components.push_back(comp); }
std::list<NLComponent*> getComponent() { return components; }
private: std::list<NLComponent*> components; };
inline ostream& operator<<(ostream& s, const NLComponent& c) { return c.print(s); }
int main(int argc, char *argv[]) { NewsLetter news; news.addComponent(new TextBlock("../report_text.txt")); news.addComponent(new Graphic("../event_image.png"));
NewsLetter report(news);
for (auto it : report.getComponent()) { cout << *it; } }
|
AI改进后的代码:
问题1:内存安全问题(最严重)
当前设计使用原始指针管理内存,容易导致内存泄漏。当addComponent传入new创建的对象时,所有权关系不明确,如果NewsLetter对象未被正确销毁或发生异常,会造成内存泄漏。
改进方案:使用智能指针管理生命周期
问题2:缺少虚析构函数
NLComponent基类声明了纯虚函数,但没有显式声明虚析构函数。当通过基类指针删除派生类对象时,会导致未定义行为,派生类的析构函数不会被调用。
改进方案:在基类中显式声明虚析构函数
问题3:拷贝构造函数和赋值操作符不完整
当前只实现了拷贝构造函数,但缺少拷贝赋值运算符、移动构造函数和移动赋值运算符。这会导致使用赋值操作时出现浅拷贝问题,也无法利用移动语义优化性能。
改进方案:遵循Rule of Five,完整实现或显式默认/删除所有特殊成员函数
问题5:资源管理接口不明确
addComponent接收原始指针参数,暗示转移所有权但不明确。调用者不清楚是否需要自己释放内存,容易造成重复释放或内存泄漏。
改进方案A:使用智能指针明确所有权;
改进方案B:提供工厂方法模板,避免手动new
问题6:getComponent接口设计问题
当前getComponent返回内部容器的拷贝,存在三个问题:暴露了list实现细节;返回原始指针破坏了封装;非const版本允许外部修改内部状态。
改进方案:提供const只读访问或迭代器接口
问题7:缺少const正确性
getComponent只有非const版本,无法通过const对象调用。同时返回非const指针允许调用者修改内部组件状态。
改进方案:提供const重载版本
问题8:print虚函数设计
当前print返回ostream引用,支持链式调用,但作为虚函数,签名固定后难以修改。如果未来需要支持不同输出目标(如文件、字符串),灵活性不足。
改进方案:考虑使用访问者模式或将序列化逻辑分离
或者保持现状,但确保派生类实现中始终返回传入的流引用。
#include <iostream> #include <list> #include <memory> #include <string>
class NLComponent { public: virtual ~NLComponent() = default; virtual std::unique_ptr<NLComponent> clone() const = 0; virtual void print(std::ostream& s) const = 0; };
class TextBlock : public NLComponent { public: explicit TextBlock(std::string path) : textPath(std::move(path)) {} std::unique_ptr<NLComponent> clone() const override { return std::make_unique<TextBlock>(*this); } void print(std::ostream& s) const override { s << "TextBlock:\n" << textPath << "\n"; }
private: std::string textPath; };
class Graphic : public NLComponent { public: explicit Graphic(std::string path) : imgPath(std::move(path)) {} std::unique_ptr<NLComponent> clone() const override { return std::make_unique<Graphic>(*this); } void print(std::ostream& s) const override { s << "Graphic:\n" << imgPath << "\n"; }
private: std::string imgPath; };
class NewsLetter { public: NewsLetter() = default; NewsLetter(const NewsLetter& rhs) { for (const auto& comp : rhs.components) { components.push_back(comp->clone()); } } NewsLetter& operator=(const NewsLetter& rhs) { if (this != &rhs) { NewsLetter tmp(rhs); swap(tmp); } return *this; } NewsLetter(NewsLetter&&) noexcept = default; NewsLetter& operator=(NewsLetter&&) noexcept = default; void swap(NewsLetter& rhs) noexcept { components.swap(rhs.components); } void add(std::unique_ptr<NLComponent> comp) { components.push_back(std::move(comp)); } template<typename T, typename... Args> void emplace(Args&&... args) { components.push_back(std::make_unique<T>(std::forward<Args>(args)...)); } const auto& components() const { return components; }
private: std::list<std::unique_ptr<NLComponent>> components; };
inline std::ostream& operator<<(std::ostream& s, const NLComponent& c) { c.print(s); return s; }
int main() { NewsLetter news; news.emplace<TextBlock>("../report_text.txt"); news.emplace<Graphic>("../event_image.png"); NewsLetter report = news; for (const auto& comp : report.components()) { std::cout << *comp; } }
|