C++:模板参数循环依赖

发布于 2024-08-18 21:50:05 字数 880 浏览 6 评论 0原文

这更像是一个最佳实践问题,而不是一个语言问题本身,因为我已经对 C++ 中常见的绊脚石有了一个有效的解决方案。

我正在处理模板参数替换中的典型循环依赖问题。我有以下一对类:

template<class X>
class A { /* ... */ };

template<class X>
class B { /* ... */ };

并且我想将每个类实例化如下:

// Pseudocode -- not valid C++.
A<B> a;
B<A> b;

也就是说,我想将A“绑定”到B,将B“绑定”到A。

我可以通过粗略的方式解决问题使用继承技巧进行前向声明:

class sA;
class sB;

class sA : public A<sB> { /* ... */ };
class sB : public B<sA> { /* ... */ };

但这会带来一系列问题,因为 sAsB 并不是真正的 AB< /代码>。例如,如果没有正确地将 A 的构造函数复制到 sA 中,或者以某种方式围绕代码进行闪闪发光的强制转换,我就无法调用 A 的构造函数。

我的问题是:处理这个问题的最佳实际方法是什么?有什么特别巧妙的解决方案来解决这个问题吗?

我同时使用 MSVC2008 和 G++,但欢迎使用具有特定于编译器的扩展的解决方案。

谢谢,

亚历克

This is more a best practice question than a language question in itself, since I already have a working solution to what seems to be a common stumbling block in C++.

I'm dealing with a typical cyclic dependency issue in template parameter substitutions. I have the following pair of classes:

template<class X>
class A { /* ... */ };

template<class X>
class B { /* ... */ };

and I want to instantiate each one as the following:

// Pseudocode -- not valid C++.
A<B> a;
B<A> b;

that is, I want to 'bind' A to B, and B to A.

I can solve the problem, in a gross way, through a forward declaration with inheritance trick:

class sA;
class sB;

class sA : public A<sB> { /* ... */ };
class sB : public B<sA> { /* ... */ };

but this brings in a set of problems, since sA and sB are not indeed A and B. For example, I cannot invoke A's constructors without properly duplicating them into sA, or somehow sparkling casts around the code.

My question is: what is the best practical way to deal with this issue? Any specially clever solution to this problem?

I am using both MSVC2008 and G++, but solutions with compiler-specific extensions are welcome.

Thanks,

Alek

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

情深如许 2024-08-25 21:50:05

如前所述,处理此问题的最佳实用方法是重构 - 通过解耦打破依赖关系。

可能的选项包括:

  • 使用虚拟方法的接口
  • 静态接口(可能使用类型特征或概念检查)
  • 在一侧或两侧使用回调(可能通过函子或信号缓解)

当您的需求突然发生变化时,这也可以帮助您。假设您在某些情况下需要一个特殊的服务器 - 它当然应该支持您已经编写的所有客户端,您不想重写它们。或者在某些情况下您需要一些特殊的客户端...
使用您的方法,这将需要重写两侧,而解耦方法只需编写您需要更改的一侧的修改版本即可。

例如,采用客户端的静态方法:

template<class server>
class client {
    server& srv;
public:
    client(server& srv) : srv(srv) {};
    void work(const request& req) {
        srv.add_job(make_job(req));
    }
};

这里,client甚至不需要知道server的具体类型 - 如果它没有成员函数add_job(job&) 或兼容的东西,编译将会失败。
如果您想变得更正式,您可以研究静态断言和概念检查。

As mentioned, the best practical way to deal with this is to refactor - break the dependencies by decoupling.

Possible options include:

  • interfaces by using virtual methods
  • static interfaces (possibly using type traits or concept checks)
  • using callbacks on one or both sides (possible eased via functors or signals)

This also helps you whenever your requirements suddenly change. Suppose you need a special server in some cases - it should of course support all the clients you've already written, you don't want to rewrite them. Or you need some special clients in some cases...
With your approach this would require rewriting both sides, whith a decoupled approach its just a matter of writing a modified version of the side you need to change.

Taking e.g. the static approach with clients:

template<class server>
class client {
    server& srv;
public:
    client(server& srv) : srv(srv) {};
    void work(const request& req) {
        srv.add_job(make_job(req));
    }
};

Here, client doesn't even need to know the concrete type of server - if it doesn't have a member function add_job(job&) or something compatible, compilation will simply fail.
If you want to get more formal, you could look into static assertions and concept checks.

伪心 2024-08-25 21:50:05

由于模板的类型命名了它的所有参数,因此不能有参数化的无限循环。

您可能(当然)只是试图同时向相反的方向发送信息。这没有问题,但是你不能将信息封装在提供实现的类中。

template< class W > // define an abstract class to pass data
struct widget_traits {};

template<>
struct widget_traits< SpringySpring > { // specialize to put data in it
    struct properties { … };
    enum { boing = 3 };
};

template< class V >
struct veeblfetzer_traits {};

template<>
struct veeblfetzer_traits< VonNeumann > {
    typedef int potrzebie;
};

template< struct WT, struct VT > // pass info by using as argument
struct MyWidget { … };

template< struct VT, struct WT > // both ways
struct MyVeeblfetzer { … };

Since a template's type names all its parameters, you can't have an endless loop of parameterization.

You are probably (certainly) just trying to send information in opposite directions at the same time. There's no problem with that, but you can't encapsulate the information in the classes that provide implementation.

template< class W > // define an abstract class to pass data
struct widget_traits {};

template<>
struct widget_traits< SpringySpring > { // specialize to put data in it
    struct properties { … };
    enum { boing = 3 };
};

template< class V >
struct veeblfetzer_traits {};

template<>
struct veeblfetzer_traits< VonNeumann > {
    typedef int potrzebie;
};

template< struct WT, struct VT > // pass info by using as argument
struct MyWidget { … };

template< struct VT, struct WT > // both ways
struct MyVeeblfetzer { … };
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文