gpt4 book ai didi

c++ - 濒临UB - 服务器/客户端内存删除程序

转载 作者:行者123 更新时间:2023-11-28 02:35:45 25 4
gpt4 key购买 nike

我有几个不相关的类使用监听器协同工作。他们都保留了各自虚拟界面的拷贝,简化如下:

    struct Base
{
struct IFormat
{
virtual int formatVal(string &) = 0;
};

void addFmt(IFormat * fmt)
{
if (!fmts.contains(fmt))
fmts.push_back(fmt);
}

void removeFmt(IFormat * fmt)
{
auto it = fmts.find(fmt);
if (it != fmts.end())
fmts.erase(it);
}

int getValueFromFmts(string & input)
{
// this is getting called once in a while
int ret(0);
for (auto fmt : fmts)
ret += fmt->formatVal(input);
}

virtual ~Base()
{
// what now? something probably has a reference to me.
}
private:
vector<IFormat *> fmts;

};


struct Editor : public Base::IFormat
{
Editor(Base * base)
: base(base)
{
base->addFmt(this);
}

~Editor()
{
// base might be deleted!
base->removeFmt(this);
}

virtual int formatVal(string &) override { ... }

private:
Base * base;
};

问题,如代码中所述:任一类都可能随时被删除。编辑器可以注销它的格式化程序,但如果基类先消失,事情就会变得糟糕。这是一个我有很多地方的问题(它是 GUI 代码),所以我需要一个通用的解决方案。我认为所有“基”类都可以从通用删除通知程序(服务器)派生,并将通知机制实现为该服务器的客户端。我创建了这段代码,效果非常好:

template<typename Derived>
class DestructionServer
{
public:
typedef Derived type;

class ObjectProxy
{
public:
ObjectProxy(const Derived * serverToPresent)
: server(serverToPresent)
{}

bool operator == (const Derived * other) const
{
return server == other;
}

bool operator != (const Derived * other) const
{
return server != other;
}

private:
const Derived * server;
};

class Client
{
friend class DestructionServer<Derived>;
public:
Client()
: server(nullptr)
{

}

typedef DestructionServer Server;

virtual ~Client()
{
if (server)
server->removeClientDestructor(this);
}

virtual void onObjectDestruction(const ObjectProxy & destroyedObject) = 0;

private:
void onDestruction(const Derived * derivedServer)
{
if (!server)
throw std::runtime_error("Fatal error: DestructionServer::Client has no server!");
// derivedServer should be able to downcast to server, without conversion
if (derivedServer != server)
throw std::runtime_error("Fatal error: derivedServer does not derive from server!");

// forget reference to server
server = nullptr;
// return an unmodifiable reference to the server
onObjectDestruction(derivedServer);
}
Server * server;
};


void removeClientDestructor(Client * client)
{
auto it = std::find(clients.begin(), clients.end(), client);
if (it != clients.end())
{
clients.erase(it);
}
}

void addClientDestructor(Client * client)
{
if (client && !std::contains(clients, client))
{
clients.push_back(client);
// this only happens if a client is loaded into multiple servers
// or the client is trying to add itself multiple times to the same server.
if (client->server)
throw std::runtime_error("Fatal error: Client already has a server!");
client->server = this;
}
}

virtual ~DestructionServer()
{
for (Client * client : clients)
{
// would like to use a dynamic_cast here to check the upcast,
// but it is not possible since
// ((Derived*)this) is actually deconstructed at this point...
// or can static_cast handle this?
// in effect this is UB, but it 'works'
if (const Derived * derivedServer = static_cast<const Derived *>(this))
{
client->onDestruction(derivedServer);
}
else
{
// in fact, the typeid() shouldn't work as well, here?
throw std::runtime_error(
std::string("Fatal error: ") + typeid(this).name() +
" doesn't derive from " + typeid(DestructionServer<Derived> *).name()
);
}
}

}
protected:
// make it impossible to construct this class without
// deriving from this class.
DestructionServer() {};

private:
std::vector<Client *> clients;
};

现在我可以像这样重写示例,并使其完全删除安全:

    struct Base : public DestructionServer<Base>
{
struct IFormat : public Client
{
virtual int formatVal(string &) = 0;
};

void addFmt(IFormat * fmt)
{
if (!fmts.contains(fmt))
{
addClientDestructor(fmt);
fmts.push_back(fmt);
}
}

void removeFmt(IFormat * fmt)
{
auto it = fmts.find(fmt);
if (it != fmts.end())
{
fmts.erase(it);
}
}

int getValueFromFmts(string & input)
{
// this is getting called once in a while
int ret(0);
for (auto fmt : fmts)
ret += fmt->formatVal(input);
}

virtual ~Base()
{
// DestructionServer automatically notifies all
// objects that set a formatter to this class.
}
private:
vector<IFormat *> fmts;

};


struct Editor : public Base::IFormat
{
Editor(Base * base)
: base(base)
{
base->addFmt(this);
}

~Editor()
{
// base is now null if it is deleted
if(base)
base->removeFmt(this);
}

virtual int formatVal(string &) override { ... }
virtual void onObjectDestruction(const Base::ObjectProxy & object)
{
if (object == base)
{
// ok, base is destructed now. set it to null and go kill our self
base = nullptr;
delete this;
}
}
private:
Base * base;
};

问题

如果您通过了代码墙并阅读了评论 - 谢谢 - 您可能注意到我正在将 this 指针从基本解构函数转换为派生类型,并回调通知程序。完全清楚指针很可能是垃圾(半解构),我设计了 ObjectProxy 类来防止客户端使用该对象(尽管如果他们有拷贝,他们仍然可以,显然)。

ObjectProxy 唯一允许的是将指针的值与另一个相同类型的指针进行比较(这样客户可以检查哪个对象被破坏了),所以我不确定它是否真的是未定义的行为 - 它是从不取消引用指针。

此外,upcast 是否会在运行时失败 - 我需要 dynamic_cast 吗?我很确定 static_cast 不允许将不相关的模板类型提供给 DestructionServer,因此错误的类型将在编译时失败。

也欢迎任何其他使这个系统工作的想法......

最佳答案

看起来像是 std::shared_ptr 的典型案例.显然,您需要显式重置共享指针以防止循环引用,或者使用 std::weak_ptr朝一个方向。当前的解决方案似乎过于复杂。

[编辑]你甚至可以研究一种奇特的技术。您可以有多个 shared_ptr不同类型的共享单次使用计数。因此,shared_ptr<IFormat>可以与 shared_ptr<Base> 共享使用次数.因此,您可以分发指向客户端代码的指针,甚至是 shared_ptr<IFormat> 的用户。会保留Base活。既然你设置了Base拥有IFormat ,一切都变得微不足道。

关于c++ - 濒临UB - 服务器/客户端内存删除程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27537450/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com