【问题标题】:Does C++ support abstract array lengthsC++ 是否支持抽象数组长度
【发布时间】:2016-04-14 19:22:53
【问题描述】:

如果我有一个基类 A,我希望能够在 A 中编写代码,该代码使用一个大小由其子级决定的数组

我希望能够拥有一个指向 A 的指针数组,但我不想为每个长度创建单独的成员函数

我能想到的最好的方法如下:

class A
{
   char type;
   int * values;
   int calc(int pos);  // implementation is not relevant to question
public:
   A (int * arr) : values(arr) {}
   int foo(int pos)
   {
      int index=calc(pos);
      return values[index];
   }
};

template <size_t X>
class B : public A
{
    int vals[X];
public:
    B() : A(vals) {}
};

template<>
class B<0>;  // don't want 0 allowed

这允许A访问一个由孩子决定大小的数组,它是连续的内存

但是,它浪费了values 指针的空间并混淆了编译器可以用于优化的信息,因为在实现时它不必是子级传递构造的连续内存,但我想要求连续记忆。

理想情况下,我想直接在A中使用偏移量

C 中,这与int values[] 相同,但没有C++ 等效项

【问题讨论】:

  • 基类可以是模板吗?
  • 代码是错误的。指向数组的指针不是指向其数据类型的指针!
  • std::vector<int> 可能吗?
  • 如果B分配的内存不连续,A如何安全访问个别成员?为此,B 应该提供一个带有类似int&amp; value_at(size_t offset) 原型的 getter。
  • 向量应该和数组一样快。它基本上是一样的。事实上,它的内部完全一样。

标签: c++ arrays inheritance


【解决方案1】:

是的,你可以,使用模板:

template<std::size_t size>
struct A {
    A(std::array<int, size> _values) : values{_values} {}

private:
    std::array<int, size> values;
};

struct B : A<4> {
    using A<4>::A;
};

然后,你可以像这样使用你的类:

B myB{5, 6, 3, 2};

std::array 在堆栈上分配内存,或者直接在结构中分配内存,就像固定数组一样。您可以通过比较大小来测试这一点。

如果你需要一个通用的基类,你可以这样做:

struct C {
    virtual ~C() {}
    virtual int* data();
    virtual std::size_t size();
};

然后覆盖A中的论文:

template<std::size_t size>
struct A : C {
    A(std::array<int, size> _values) : values{_values} {}

    int* data() override {
        return values.data();
    }

    std::size_t size() override {
        return size;
    }

private:
    std::array<int, size> values;
};

【讨论】:

  • 不是反对者,但要注意所谓的堆栈。 new B() 没有将 values 放入堆栈。
  • 我所说的栈是本地栈,因为声明B的大小受数组大小的影响。
  • 对此的一个警告是,子类不会与使用不同大小的其他子类共享一个公共基类。
  • 如果A 继承自C,那么A 的任何实例化或任何A 的子类都将共享公共基类C
  • 我更正了我的回答中的错误,感谢指出,忘记了A前面的: C {
【解决方案2】:

实现这一点的经典方法是使用继承和虚拟成员

class A {
    virtual int& value_at(size_t pos);
    // other interesting methods
};

// subclass of A that uses std::vector for storage
class B: public A {
    std::vector<int> storage;
    int& value_at(size_t pos) {
        return storage[pos];
    }
};

// subclass of A that uses a fixed-size array for storage
template<int N>
class C: public A {
    int storage[N];
    int& value_at(size_t pos) {
        return storage[pos];
    }
};

B b;     // ...initialize b...
C<10> c; // ...initialize c...
A *a1 = &b;
A *a2 = &c;
// call a1->value_at(), a2->value_at() to access arrays
// transparently, regardless of storage (along with other
// public methods of A).

这种方法需要A::value_at 通过虚拟表或等效机制进行调度。如果在编译时就知道将使用哪种存储策略,则可以将A 设为模板类:

template<typename T>
class A: public T {
    // other interesting methods, that use value_at() from T
};

class vec_storage {
    std::vector<int> storage;
public:
    int& value_at(size_t pos) {
        return storage[pos];
    }
};

// subclass of A that uses a fixed-size array for storage
template<int N>
class array_storage {
    int storage[N];
public:
    int& value_at(size_t pos) {
        return storage[pos];
    }
};

A<vec_storage> b;
A<array_storage<10>> c;

在此示例中,bc 在运行时将在没有额外间接的情况下执行,但代价是没有泛型基类,因此无法向期望某些 A &amp; 的函数传递引用发送至bc

【讨论】:

  • 经典我不同意,因为这是相当低效的。问题中的设计性能要高得多。
  • @MSalters 性能更高,但不满足(规定的)要求——子类可以实现非连续存储。
  • 我试图在最后重述那部分,目标是分配连续存储
【解决方案3】:

正如 Fred Larson 指出的那样,对于更改对象数组大小的基本功能,您可能正在寻找 模板。其实很简单……

template <const int arraySize>
class MyClass
{
    private:
        int vals[arraySize];
}

此时,您甚至不需要任何派生类。一切顺利,可以随时使用任何尺寸

但是请记住,MyClass 在这种情况下不是有效类型。因为这是一个模板,所以必须指定大小。

MyClass wrong; //This will throw an error. MyClass is not a type.
MyClass<64> right; //This is a proper use of the type.

如果您需要额外的能力来存储这些不同大小的对象,您可以使用虚拟[但非抽象]基类将其与继承相当容易地结合起来。

(请忽略笨拙的设计,因为我没有费心在这个例子中定义我的构造函数/析构函数。)

class BaseClass
{
    public:
        BaseClass(){}
        virtual int access(int i)
        {
            return 0;
        }
        virtual ~BaseClass(){}
};

template <const int arraySize>
class MyClass : public BaseClass
{
    public:
        MyClass(){}
        int access(int i)
        {
            return vals[i];
        }
        ~MyClass(){}
    private:
        int vals[arraySize];
};

警告:如果您希望能够将派生类存储在向量中,那么基类不能是抽象的,这一点至关重要。见this question

当然,在本例中,您可能希望创建用于访问数组的包装函数。

【讨论】:

  • 我进行了编辑以满足额外的需求。很抱歉忽略了这一点。
猜你喜欢
  • 2012-01-25
  • 2014-07-14
  • 2011-12-13
  • 2018-07-05
  • 1970-01-01
  • 2018-10-03
  • 2013-06-06
  • 2018-02-22
相关资源
最近更新 更多