继承与专业化

发布于 2024-07-16 01:49:01 字数 868 浏览 7 评论 0原文

考虑以下两种使用场景(正如您所看到的,即最终用户只会对使用 Vector2_tVector3_t 感兴趣):

[1]继承:

template<typename T, size_t N> struct VectorBase
{
};

template<typename T> struct Vector2 : VectorBase<T, 2>
{
};

template<typename T> struct Vector3 : VectorBase<T, 3>
{
};

typedef Vector2<float> Vector2_t;
typedef Vector3<float> Vector3_t;

[2]专业化:

template<typename T, size_t N> struct Vector
{
};

template<typename T> struct Vector<T, 2>
{
};

template<typename T> struct Vector<T, 3>
{
};

typedef Vector<float, 2> Vector2_t;
typedef Vector<float, 3> Vector3_t;

我无法决定哪个是更好的解决方案。 继承的明显优点是派生类中的代码重用; 一个可能的缺点是性能(更大的尺寸,用户可能按值传递等)。 专业化似乎可以避免这一切,但代价是我不得不多次重复自己。

我还错过了哪些其他优点/缺点?您认为我应该走哪条路?

Considering the following two usage scenarios (exactly as you see them, that is, the end-user will only be interested in using Vector2_t and Vector3_t):

[1]Inheritance:

template<typename T, size_t N> struct VectorBase
{
};

template<typename T> struct Vector2 : VectorBase<T, 2>
{
};

template<typename T> struct Vector3 : VectorBase<T, 3>
{
};

typedef Vector2<float> Vector2_t;
typedef Vector3<float> Vector3_t;

[2]Specialization:

template<typename T, size_t N> struct Vector
{
};

template<typename T> struct Vector<T, 2>
{
};

template<typename T> struct Vector<T, 3>
{
};

typedef Vector<float, 2> Vector2_t;
typedef Vector<float, 3> Vector3_t;

I can't make up my mind as to which is a nicer solution.
The obvious advantage to inheritance is code reuse in the derived classes; a possible disadvantage being performance (bigger size, users may pass by value, etc).
Specialization seems to avoid all that, but at the expense of me having to repeat myself multiple times.

What other advantages/disadvantages did I miss, and in your opinion, which route should I take?

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

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

发布评论

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

评论(5

一曲琵琶半遮面シ 2024-07-23 01:49:01

我认为你最终想要的是拥有用户类型

Vector<T, N>

并且根据N,用户会得到稍微不同的东西。 第一个无法实现这一点,但第二个可以,但代价是代码重复。

您可以做的是反转继承:

template<typename T, size_t N> struct VectorBase 
{
};

template<typename T> struct VectorBase<T, 2>
{
};

template<typename T> struct VectorBase<T, 3>
{
};

template<typename T, size_t N> struct Vector : VectorBase<T, N>
{
};

并实现仅依赖于 N 是适当基类中的某个特定值的少数函数。 您可以在其中添加受保护的析构函数,以防止用户通过指向 VectorBase 的指针删除 Vector 的实例(通常他们甚至不应该能够命名 VectorBase code>:将这些基础放在某个实现命名空间中,例如 detail)。

另一个想法是将这个解决方案与另一个答案中提到的解决方案结合起来。 私有继承(而不是如上所述的公开继承)并将包装器函数添加到调用基类的实现的派生类中。

另一种想法是仅使用一个类,然后使用 enable_if(使用 boost::enable_if)针对特定的 N 值启用或禁用它们,或者使用像这样简单得多的 int 到类型转换器

struct anyi { };
template<size_t N> struct i2t : anyi { };

template<typename T, size_t N> struct Vector
{
    // forward to the "real" function
    void some_special_function() { some_special_function(i2t<N>()); }

private:
    // case for N == 2
    void some_special_function(i2t<2>) {
        ...
    }

    // case for N == 3
    void some_special_function(i2t<3>) {
        ...
    }

    // general case
    void some_special_function(anyi) {
        ...
    }
};

这样,它对 Vector 的用户是完全透明的。 它也不会为编译器进行空基类优化(很常见)增加任何空间开销。

What you ultimately want, i think, is to have the user type

Vector<T, N>

And depending on N, the user will get slight different things. The first will not fulfill that, but the second will, on the price of code duplication.

What you can do is to invert the inheritance:

template<typename T, size_t N> struct VectorBase 
{
};

template<typename T> struct VectorBase<T, 2>
{
};

template<typename T> struct VectorBase<T, 3>
{
};

template<typename T, size_t N> struct Vector : VectorBase<T, N>
{
};

And implement the few functions that depend only on N being some specific value in the appropriate base-class. You may add a protected destructor into them, to prevent users deleting instances of Vector through pointers to VectorBase (normally they should not even be able to name VectorBase: Put those bases in some implementation namespace, like detail).

Another idea is to combine this solution with the one mentioned in another answer. Inherit privately (instead of publicly as above) and add wrapper functions into the derived class that call the implementations of the base-class.

Yet another idea is to use just one class and then enable_if (using boost::enable_if) to enable or disable them for particular values of N, or use a int-to-type transformer like this which is much simplier

struct anyi { };
template<size_t N> struct i2t : anyi { };

template<typename T, size_t N> struct Vector
{
    // forward to the "real" function
    void some_special_function() { some_special_function(i2t<N>()); }

private:
    // case for N == 2
    void some_special_function(i2t<2>) {
        ...
    }

    // case for N == 3
    void some_special_function(i2t<3>) {
        ...
    }

    // general case
    void some_special_function(anyi) {
        ...
    }
};

That way, it is completely transparent to the user of Vector. It also won't add any space overhead for compilers doing the empty base class optimization (quite common).

半衬遮猫 2024-07-23 01:49:01

使用继承和私有继承。 并且不要使用任何虚函数。 由于使用私有继承,您没有 is-a,因此没有人能够使用指向派生子类的 baseclas 指针,并且在按值传递时不会遇到切片问题。

这为您提供了两全其美的好处(事实上,这就是大多数库实现许多 STL 类的方式)。

来自 http://www.hackcraft.net/cpp/templateInheritance/ (讨论 std: :vector,而不是您的 Vector 类):

vector 被声明为具有
vector 的私有基数。
所有放置新元素的函数
进入向量,例如push_back(),调用
这个私人基地上的等效功能,
所以在内部我们的向量正在使用
用于存储的向量。 所有功能
它从向量中返回一个元素,例如
front(),在
调用等效函数的结果
在私人基地上。 因为获得的唯一途径是
指向 vector 的指针(除了
故意危险的伎俩)是通过
vector 提供的接口是安全的
void* 静态转换回 T*
(或将 void*& 返回到 T*&,依此类推)。

一般来说,如果 STL 这样做,它似乎是一个不错的模拟模型。

Use inheritance and private inheritance. And don't use any virtual functions. Since with private inheritance, you don't have is-a, no one will be able to use a baseclas pointer to a derived subclass, and you won't get the slicing problem when passinfg by value.

This gives you the best of both worlds (and indeed it's how most libraries implement many of the STL classes).

From http://www.hackcraft.net/cpp/templateInheritance/ (discussing std::vector, not your Vector class):

vector<T*> is declared to have a
private base of vector<void*>.
All functions which place a new element
into the vector, such as push_back(), call the
equivalent function on this private base,
so internally our vector<T*> is using a
vector<void*> for storage. All functions
which return an element from the vector, such as
front(), perform a static_cast on the
result of calling the equivalent function
on the private base. Since the only way to get a
pointer into the vector<void*> (apart from
deliberately dangerous tricks) is through the
interface offered by vector<T*> it is safe
to staticly cast the void* back to T*
(or the void*& back to T*&, and so on).

In general, if the STL does it like this, it seems like a decent model to emulate.

温馨耳语 2024-07-23 01:49:01

继承只能用于建模“is-a”。 专业化将是更清洁的选择。 如果您出于某种原因需要或想要使用继承,至少将其设为私有或受保护的继承,这样您就不会从具有公共非虚拟析构函数的类公开继承。

是的,模板元编程人员总是这样做,

 struct something : something_else {};

但那些东西元函数,并不意味着用作类型。

Inheritance should only be used to model "is-a". Specialization would be the cleaner alternative. If you need or want to use inheritance for whatever reason, at the very least make it private or protected inheritance, so you don't inherit publicly from a class with a public non-virtual destructor.

Yes, the template metaprogramming guys always do

 struct something : something_else {};

But those somethings are metafunctions and not meant to be used as types.

悸初 2024-07-23 01:49:01

您需要根据您的实际用例以及真正的公共接口应该是什么来确定以下问题的“正确”答案:

  1. Vector”是您的主要接口吗?公共接口还是实现细节? 例如,人们是否应该使用这些对象,例如 std::vector 或者它只是隐藏“vector_2”真实 API 的常见实现逻辑的一种方法,等等.?


  2. 所有“Vector”是否具有相同的逻辑行为? 大多数人会告诉您在进行此类决策时遵循 STL,但 STL 本身却因 std::vector 而臭名昭著地搞砸了这一点。 他们被迫支持由此造成的混乱局面 30 年。

需要记住的一件事是,您可以将继承和模板特化结合起来; 它们并不相互排斥。 模板专业化应该对用户透明,这意味着它们应该具有与通用模板相同的公共接口和行为。

例如:

template <typename T, size_t N>
class VectorBase {
public:
    // The public interface all 'vector-like' classes must honor
    virtual T& at(size_t index) = 0;

    // More APIs... 
};

// The generic Vector<T,N> 
// Let's forbid inheritance from vectors, for now. 
template <typename T, size_t N> 
class Vector final : public VectorBase<T, N>
{
public: 
   // Have to implement the public interface 
   virtual T& at(size_t index) override;

   // Vector-only APIs
   virtual T* data();
};

// We've got some optimized way of storing, let's say doubles, that doesn't fit the general case
template <size_t N>
class Vector<double, N> final : public VectorBase<double, N>
{
public:
   // Same APIs as baseline Vector<T,N>
   virtual double& at(size_t index) override; 
};

// We DISABLE a specialization, e.g. Vector<bool, N> 
template <size_t N>
class Vector<bool, N> final : public VectorBase<bool, N>
{
};  // Nobody can ever create one of these. We never implemented the pure virtual APIs and we disabled inheritance.

// A different object that has a lot of interface overlap with Vector<T, N>
template <size_t N> 
class Bitset : public Vector<bool, N> 
{
public: 
   // From VectorBase 
   virtual bool& at(size_t index) override; 

   // Unique to me 
   virtual void  bitwise_not(); 
   virtual void  bitwise_or(const Bitset<N>& other); 
   //...
};

在通用“Vector”之间共享代码 及其专业,您有多种选择。 最直接的方法是将其放入基类的受保护函数中。

You need to decide the 'correct' answer to the following questions, based on your actual use case and what the real public interface should be:

  1. Is "Vector<T,N>" your main public interface or an implementation detail? As in, are people supposed to use these objects like std::vector or is it simply a way to hide common implementation logic for the real API of "vector_2", etc.?

  2. Will all "Vector<T,N>" have the same logical behavior? Most people will tell you to follow the STL when it comes to these types of decisions, but the STL itself infamously screwed this up with std::vector<bool>. And they've been forced to support the resulting mess for 30 years.

One thing to bear in mind is that you can combine both inheritance and template specialization; they're not mutually exclusive. And template specializations should be transparent to users, meaning they should have the same public interface and behavior as the general template.

For example:

template <typename T, size_t N>
class VectorBase {
public:
    // The public interface all 'vector-like' classes must honor
    virtual T& at(size_t index) = 0;

    // More APIs... 
};

// The generic Vector<T,N> 
// Let's forbid inheritance from vectors, for now. 
template <typename T, size_t N> 
class Vector final : public VectorBase<T, N>
{
public: 
   // Have to implement the public interface 
   virtual T& at(size_t index) override;

   // Vector-only APIs
   virtual T* data();
};

// We've got some optimized way of storing, let's say doubles, that doesn't fit the general case
template <size_t N>
class Vector<double, N> final : public VectorBase<double, N>
{
public:
   // Same APIs as baseline Vector<T,N>
   virtual double& at(size_t index) override; 
};

// We DISABLE a specialization, e.g. Vector<bool, N> 
template <size_t N>
class Vector<bool, N> final : public VectorBase<bool, N>
{
};  // Nobody can ever create one of these. We never implemented the pure virtual APIs and we disabled inheritance.

// A different object that has a lot of interface overlap with Vector<T, N>
template <size_t N> 
class Bitset : public Vector<bool, N> 
{
public: 
   // From VectorBase 
   virtual bool& at(size_t index) override; 

   // Unique to me 
   virtual void  bitwise_not(); 
   virtual void  bitwise_or(const Bitset<N>& other); 
   //...
};

To share code between the generic "Vector<T,N>" and its specializations, you have several options. The most straightforward way is the put it in protected functions of the base class.

美人迟暮 2024-07-23 01:49:01

如果您过多地使用模板专业化,您可能需要重新考虑您的设计。 考虑到您将其隐藏在 typedef 后面,我怀疑您是否需要它。

If you're using template specialization too much, you probably need to rethink your design. Considering that you're hiding it behind a typedef, I doubt you need it.

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