松耦合隐式转换

时间:2011-01-15 20:37:33

标签: c++ boost-mpl enable-if

当类型在语义上等效时,隐式转换非常有用。例如,假设两个库以相同的方式实现类型,但在不同的命名空间中。或者只是一种大多数相同的类型,除了一些语义糖在这里和那里。现在,您无法将一种类型传递给设计为使用另一种的函数(在其中一个库中),除非该函数是模板。如果不是,你必须以某种方式将一种类型转换为另一种类型。这应该是微不足道的(或者其他类型在后面都不是那么相同!)但是调用转换显然会使代码膨胀,而且函数调用几乎毫无意义。虽然这样的转换函数实际上可能会复制一些值,但它们基本上不会从高级“程序员”的角度做任何事情。

隐式转换构造函数和运算符显然可以提供帮助,但它们会引入耦合,因此其中一种类型必须知道另一种类型。通常,至少在处理库时,情况并非如此,因为其中一种类型的存在使另一种类型变得冗余。此外,您不能总是更改库。

现在我看到有关如何在用户代码中进行隐式转换的两个选项:

  1. 第一个是提供代理类型,它为所有涉及的类型实现转换运算符和转换构造函数(和赋值),并始终使用它。

  2. 第二个需要对库进行最小的更改,但允许很大的灵活性: 为每个可以在外部选择启用的相关类型添加转换构造函数。

  3. 例如,对于类型A添加构造函数:

    template <class T> A(
      const T& src,
      typename boost::enable_if<conversion_enabled<T,A>>::type* ignore=0
    )
    {
      *this = convert(src);
    }
    

    和模板

    template <class X, class Y>
    struct conversion_enabled : public boost::mpl::false_ {};
    

    默认情况下禁用隐式转换。

    然后要在两种类型之间启用转换,请专门化模板:

    template <> struct conversion_enabled<OtherA, A> : public boost::mpl::true_ {};
    

    并实现可通过ADL找到的convert函数。

    我个人更喜欢使用第二种变体,除非有强烈反对它。

    现在回答实际问题:关联隐式转换类型的首选方法是什么?我的建议是好主意吗?两种方法都有任何缺点吗?允许这样的转换是危险的吗?如果库类实现者通常会提供第二种方法,那么它们的类型很可能会被软件复制,而这些软件很可能与它们一起使用(我在考虑使用3d渲染中间件,其中大多数软件包实现了3D矢量)。

6 个答案:

答案 0 :(得分:7)

我更喜欢你的“代理”方法,而不是其他选择,如果我对它感到烦恼的话。

事情的真相是,我发现这是所有发展领域中的一个主要问题,我倾向于避免在我与该特定库的交互之外使用任何特定于库的构造。一个例子可能是处理各种不同库中的事件/信号。我已经选择了boost作为 integral 到我自己的项目代码的东西,所以我非常有目的地使用boost :: signals2来进行我自己的项目代码中的所有通信。然后我将接口写入我正在使用的UI库。

另一个例子是字符串。每个该死的UI库都重新发明了字符串。我的所有模型和数据代码都使用标准版本,并且我为我的UI包装器提供了接口,这些接口在这些类型中工作...仅在我与UI组件直接交互的那一点上转换为UI特定版本。

这确实意味着我无法利用各种独立但相似的结构提供的大量功能,而且我正在编写大量额外的代码来处理这些转换,但它非常值得,因为如果我发现更好的图书馆和/或需要切换平台它变得更容易这样做,因为我不允许这些东西在所有的东西中清除它们。

所以基本上,我更喜欢代理方法,因为我已经在做了。我在抽象层中工作,使我远离我正在使用的任何特定库,并将这些抽象子类化为与所述库交互所需的细节。我总是这样做,所以想知道我想在两个第三方图书馆之间共享信息的一些小区域基本上已经回答了。

答案 1 :(得分:1)

您可以编写一个可以隐式转换为不兼容类型的转换器类(某些代理)。然后,您可以使用构造函数从其中一个类型生成代理,并将其传递给方法。然后,返回的代理将直接转换为所需的类型。

缺点是你必须在所有调用中包装参数。如果做得好,编译器甚至可以在不实例化代理的情况下内联完整的调用。并且类之间没有耦合。只有Proxy类需要知道它们。

我编程C ++已经有一段时间了,但代理应该是这样的:

class Proxy { 
  private:
    IncompatibleType1 *type1;
    IncompatibleType2 *type2;
    //TODO static conversion methods
  public:
    Proxy(IncompatibleType1 *type1) {
      this.type1=type1;
    }
    Proxy(IncompatibleType2 *type2) {
      this.type2=type2;
    }
    operator IncompatibleType1 * () { 
      if(this.type1!=NULL)
        return this.type1;
      else
        return convert(this.type2);
    }
    operator IncompatibleType2 * () { 
      if(this.type2!=NULL)
        return this.type2;
      else
        return convert(this.type1);
    }
}

电话总是如下:

expectsType1(Proxy(type2));
expectsType1(Proxy(type1));
expectsType2(Proxy(type1));

答案 2 :(得分:1)

  

这两种方法都有任何缺点吗?允许这样的转换是危险的吗?图书馆实施者一般在......时提供第二种方法。

一般来说,隐式转换有一个缺点,任何工作都会对那些对速度敏感的图书馆用户造成伤害(例如在内循环中使用它 - 或许没有意识到它)。当几个不同的隐式转换可用时,它还可能导致意外行为。所以我会说一般的库实现者允许隐式转换是不好的建议。

在你的情况下 - 基本上将数字元组(A)转换为另一个元组(B) - 这很容易,编译器可以内联转换并可能完全优化它。所以速度不是问题。可能也没有任何其他隐式转换来混淆事物。所以便利很可能会胜出。但是,提供隐式转换的决定应该根据具体情况进行,这种情况很少见。

像你建议的第二种变体的一般机制很少有用,并且可以很容易地做一些非常糟糕的事情。以此为例(做作但仍然):

struct A {
    A(float x) : x(x) {}
    int x;
};

struct B {
    B(int y): y(y) {}
    template<class T> B(const T &t) { *this = convert(t); }
    int y;
};

inline B convert(const A &a) {
    return B(a.x+1);
}

在这种情况下,禁用模板构造函数将更改B(20.0)的值。换句话说,仅通过添加隐式转换构造函数,您可以更改现有代码的解释。显然,那是非常危险的。所以隐式转换不应该是普遍可用的,而是为非常具体的类型提供,只有在它有价值且易于理解时才提供。保证你的第二个变种是不常见的。

总结一下:在图书馆之外做得更好,完全了解所有要支持的类型。代理对象似乎很完美。

答案 3 :(得分:0)

关于你的第一个选择:

  

提供实现的代理类型   转换运算符和   转换构造函数(和   所有参与者的任务)   类型,并始终使用它。

如果性能不是很关键(或者如果它是,而且数据基本上都是字符串),你可以使用字符串(文本)作为代理。实施运算符<<>>,您可以使用boost::lexical_cast<>使用文本中间表示进行转换:

const TargetType& foo = lexical_cast<TargetType>(bar);

显然,如果你非常关心性能,你不应该这样做,并且还有其他注意事项(这两种类型都应该有合理的文本表示),但它相当普遍,并且“只适用于”很多现有的东西

答案 4 :(得分:0)

你能使用转换操作符重载吗?如下例所示:

class Vector1 {
  int x,y,z;
public:
  Vector1(int x, int y, int z) : x(x), y(y), z(z) {}
};

class Vector2 {
  float x,y,z;
public:
  Vector2(float x, float y, float z) : x(x), y(y), z(z) {}

  operator Vector1()  {
    return Vector1(x, y, z);
  }
};

现在这些来电成功了:

void doIt1(const Vector1 &v) {
}

void doIt2(const Vector2 &v) {
}

Vector1 v1(1,2,3);
Vector2 v2(3,4,5);
doIt1(v1);
doIt2(v2);

doIt1(v2); // Implicitely convert Vector2 into Vector1

答案 5 :(得分:-2)

我今天很慢。再次使用代理模式有什么问题?我的建议是,不要花太多时间担心复制功能做不必要的工作。另外,明确是好的。