第五章,技术

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可以是TextBlock也可以是Graphic
{
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;
}
}