与c++中原型模式示例解释的混淆

confusion with example explanation of Prototype pattern in c++

本文关键字:解释 c++ 原型 模式      更新时间:2023-10-16

有人发布了关于此模式的问题,但我脑海中没有提出问题的解决方案,所以发布了我的查询。。。

在上面的例子中,如果所有的实例都是由s_prototypes变量初始化的,那么下次如果克隆方法中的任何对象都将被新对象替换,那么现有对象会发生什么??它会造成内存泄漏吗??

据我从上面的例子中了解到,有两个说法让我感到困惑

class Stooge {
public:
virtual Stooge* clone() = 0;
virtual void slap_stick() = 0;
};
class Factory {
public:
static Stooge* make_stooge( int choice );
private:
static Stooge* s_prototypes[4];
};
int main() {
vector roles;
int             choice;
while (true) {
cout << "Larry(1) Moe(2) Curly(3) Go(0): ";
cin >> choice;
if (choice == 0)
break;
roles.push_back(
Factory::make_stooge( choice ) );
}
for (int i=0; i < roles.size(); ++i)
roles[i]->slap_stick();
for (int i=0; i < roles.size(); ++i)
delete roles[i];
}
class Larry : public Stooge {
public:
Stooge*   clone() { return new Larry; }
void slap_stick() {
cout << "Larry: poke eyesn"; }
};
class Moe : public Stooge {
public:
Stooge*   clone() { return new Moe; }
void slap_stick() {
cout << "Moe: slap headn"; }
};
class Curly : public Stooge {
public:
Stooge*   clone() { return new Curly; }
void slap_stick() {
cout << "Curly: suffer abusen"; }
};
Stooge* Factory::s_prototypes[] = {
0, new Larry, new Moe, new Curly
};
Stooge* Factory::make_stooge( int choice ) {
return s_prototypes[choice]->clone();
}
Output
Larry(1) Moe(2) Curly(3) Go(0): 2
Larry(1) Moe(2) Curly(3) Go(0): 1
Larry(1) Moe(2) Curly(3) Go(0): 3
Larry(1) Moe(2) Curly(3) Go(0): 0
Moe: slap head
Larry: poke eyes
Curly: suffer abuse

但是当我们用make_stooge方法调用clone方法时,它会返回新对象,如果返回新对象并将其替换为现有对象,则现有对象将在此处创建内存泄漏(因为新操作符完全创建单独的对象,而不在此处返回现有对象)。。。。

所以这个例子让我很困惑。。。。

好吧,s_prototypes中的指针从未被删除,这是对的,但至少这些对象无论如何都要在程序的整个运行时间内持续,所以除非需要在它们的析构函数中执行特定的操作,否则在程序终止前不删除它们就不是世界末日。这远没有代码那么糟糕,因为代码有可能持续泄漏内存,这会导致程序使用的内存不断增加。

如果你愿意,你可以用静态实例代替它们,比如:

#include <iostream>
#include <vector>
using namespace std;
class Stooge {
public:
virtual Stooge* clone() = 0;
virtual void slap_stick() = 0;
virtual ~Stooge() = default;
};
class Larry : public Stooge {
public:
Stooge*   clone() { return new Larry; }
void slap_stick() {
cout << "Larry: poke eyesn"; }
};
class Moe : public Stooge {
public:
Stooge*   clone() { return new Moe; }
void slap_stick() {
cout << "Moe: slap headn"; }
};
class Curly : public Stooge {
public:
Stooge*   clone() { return new Curly; }
void slap_stick() {
cout << "Curly: suffer abusen"; }
};
class Factory {
public:
static Stooge* make_stooge( int choice );
private:
static Larry larry;
static Curly curly;
static Moe moe;
};
int main() {
vector<Stooge*> roles;
int             choice;
while (true) {
cout << "Larry(1) Moe(2) Curly(3) Go(0): ";
cin >> choice;
if (choice == 0)
break;
roles.push_back(
Factory::make_stooge( choice ) );
}
for (int i=0; i < roles.size(); ++i)
roles[i]->slap_stick();
for (int i=0; i < roles.size(); ++i)
delete roles[i];
}
Stooge* Factory::make_stooge( int choice ) {
switch(choice) {
case 1:
return larry.clone();
case 2:
return curly.clone();
case 3:
return moe.clone();
default:
return nullptr;
}
}

代码中没有内存泄漏,因为新创建的对象被小心地存储在vector(此处为roles)中。

然后从该矢量中使用它们,并在程序结束前销毁:

...
for (int i=0; i < roles.size(); ++i)
delete roles[i];
}

但在退出主管道之前使用return 0;会更好。。。

为要派生的类创建一个虚拟析构函数是一种很好的做法:如果Larry等中的任何一个都有一个非平凡的析构函数,那么当您通过基类指针删除它们时,就不会使用它

class Stooge {
public:
virtual Stooge* clone() = 0;
virtual void slap_stick() = 0;
virtual ~Stooge() {}
};

对于s_prototypes的4个静态对象,在调用main之前,它们在程序启动时被静态初始化,并且永远不会被修改。他们的clone方法被调用来创建新对象,但s_prototype仍然会指向旧对象——这就是为什么存储和删除新创建的对象很重要的原因。

这4个对象确实存在内存泄漏,因为它们永远不会被明确地销毁。以这种方式泄漏静态对象通常被认为是可以接受的,因为如果释放了所有内存,它们的生命周期会一直延长到程序结束。如果调用析构函数很重要,那么还应该为它们创建静态实例:

static Larry _larry;
static Moe _moe;
static Curly _curly;
Stooge* Factory::s_prototypes[] = {
0, &_larry, &_moe, &_curly
};

如果你用明确销毁

virtual ~Stooge() {
cout << "Delete " << this << endl;
}

并稍微改变程序的结尾:

for (int i=0; i < roles.size(); ++i)
delete roles[i];
cout << "END" << endl;
return 0;
}

输出将类似于:

...
Delete 001ADB78
END
Delete 00F350A0
Delete 00F3509C
Delete 00F35098

清楚地表明静态对象在程序结束后被破坏。