Modifying std :: vector function (inheritance?)

I'm porting some Fortran90 code to C ++ (because I'm stupid to keep "Why ?!").

Fortran allows you to specify ranges on arrays, specifically starting with negative values, for example

double precision :: NameOfArray(FirstSize, -3:3)

      

I can write this in C ++ as something like

std::array<std::array<double, 7>, FirstSize> NameOfArray;

      

but now i have to index like NameOfArray[0:FirstSize-1][0:6]

. If I want to index using Fortran style index I can write maybe

template <typename T, size_t N, int start>
class customArray
{
public:
    T& operator[](const int idx) { return data_[idx+start]; }
private:
    std::array<T,N> data_;
}

      

and then

customArray<double, 7, -3> NameOfArray;
NameOfArray[-3] = 5.2;
NameOfArray[3] = 2.5;
NameOfArray[4] = 3.14; // This is out of bounds, 
                       // despite being a std::array of 7 elements

      

So the general idea is: β€œDo not inherit from the std :: container classβ€œ here. ”I understand that this is because, for example, std :: vector does not have a virtual destructor, and therefore should not (cannot?) Be used polymorphically.

Is there any other way to use std::array

, std::vector

etc., and get their functions "free" while overriding certain functions?

template<typename T, size_t N>
T& std::array<T,N>::operator[](const int idx) { ... };

      

might allow me to override the operator, but it won't give me access to the knowledge about the custom starting point, making it completely meaningless. Also, if I optimistically thought that all my objects customArray

would have the same offset, I could hard-code that value, but then my std :: array is broken (I think).

How can I get around this? (Ignoring the simple answer - don't - just write myArray[idx-3]

as needed)

+3


source to share


5 answers


No problem with inheriting standard containers. This is just usually discouraged because it imposes several restrictions, and such inheritance is not how inheritance was originally predicted in C ++. If you're careful about these restrictions, you can safely use inheritance here.

You just need to remember that this one is not a subclass and what it actually means. In particular, you should not use pointers or references to an object of this class. The problem might be that you pass the value MyVector<x>*

where expected vector<x>*

. You should also never create objects such as dynamic (using new

), and therefore delete

these objects, with a pointer to the base class - simply because the call to the destructor will not be redirected to your class destructor, since it is not virtual.



There is no way to prevent a "derived pointer" from being cast to a "base pointer", but you can prevent a pointer from being selected from an object by operator overloading &

. You can also prevent the creation of objects of this class dynamically by declaring a private class operator new

(or = delete

work too).

Don't think about private inheritance either. It just looks like this thing is a field in a private section, except for the name of the accessor.

+6


source


The range converter class might be a solution, although you will need to do it yourself, but it will allow you to get the size of the range to initialize the vector and do the conversion.

Unverified code:



struct RangeConv // [start,end[
{
  int start, end;
  RangeConv(int s, int e) : start(s), end(e) { }
  int size() const { return end - start; }
  int operator()(int i) { return i - start; } // possibly check whether in range
}

RangeConv r(-3, 3);
std::vector<int> v(r.size());
v[r(-3)] = 5;

      

+4


source


so it shouldn't (can't?) be used polymorphically.

Don't give up too early. There are two issues in C ++ to consider when inheriting.

Lifetime

Such objects, derived classes with non-virtual destructors in the database, can be used safely in a polymorphic manner if you basically follow one simple rule: don't use delete

anywhere. This naturally means that you cannot use new

. You should generally avoid new

raw pointers as well in modern C ++. shared_ptr

will do the right job, i.e. it will safely call the correct destructor if you use make_shared

:

std:: shared_ptr<Base> bp = std:: make_shared<Derived>( /* constructor args */ );

      

The type parameter make_shared

, in this case Derived

, not only controls what type is created. It also controls which destructor is called. (Since the main shared pointer object will store the corresponding divider.)

It's tempting to use unique_ptr

, but unfortunately (by default) this will result in the wrong debitter being used (i.e. naively using delete

directly on the underlying pointer). Unfortunately, along with the standard unique_ptr

, the standard is not much secure, but less effective unique_ptr_with_nice_deleter

.

Polymorphism

Even if it std::array

had a virtual destructor, this current project would still be very strange. Since operator[]

it is not virtual, dropping from customArray*

to std:: array*

will result in an incorrect one operator[]

. This isn't really a C ++ issue, it's basically a problem that you shouldn't pretend to be customArray

isa std:: array

.

Instead, just decide what customArray

is a distinct type. This means you couldn't pass customArray*

the awaiting function std::array*

- but are you sure you want it at all?

Is there any other way to use std :: array, std :: vector, etc. and get their functions "for free" when overloading certain functions?

That's a good question. You don't want your new type to satisfy isa std::array

. You just want him to look a lot like him. It's like you've magically copied and typed all the code from std::array

to create a new type. And then you want to tweak some things.

Use private

inheritance and using

suggestions to inject the code you want:

template <typename T, size_t N, int start>
struct customArray : private std::array<T,N>
{
    // first, some functions to 'copy-and-paste' as-is
    using std::array<T,N>  :: front;
    using std::array<T,N>  :: begin;

    // finally, the functions you wish to modify
    T& operator[](const int idx) { return data_[idx+start]; }
}

      

Inheritance private

will block conversions from customArray *

to std::array *

and what we want.

PS: I have very little experience with this kind of inheritance private

. So much is not the best solution - any feedback is appreciated.

+2


source


General thought

The recommendation is not to inherit from a standard vector, because this type of construct is often misunderstood and some people are tempted to make all kinds of objects inherit from a vector, just for minor convenience.

But this rule should not become a dogma. Especially if your goal is to create a vector class, and if you know what you are doing.

Threat 1: inconsistency

If you have a very important codebase that works with vectors in the range 1..size instead of 0..size-1, you can choose to save according to this logic to avoid adding thousands of -1 to the indices, +1 to the index and + 1 for sizes.

A valid approach might be to use something like:

template <class T> 
class vectorone : public vector<T> {
    public: 
    T& operator[] (typename vector<T>::size_type n) { return vector<T>::operator[] (n-1); }
const T& operator[] (typename vector<T>::size_type n) const { return  vector<T>::operator[] (n-1); }
};

      

But you have to stay consistent throughout the vector interface:

  • First, it also exists const T& operator[]()

    . If you don't overload it, you will get wrong behavior if you have vectors in persistent objects.
  • Then, and it is missing above, there is also one at()

    that should be associated with[]

  • Then you need to take extra care with constructors as there are many to make sure your arguments are not misinterpreted.

So you have free functionality, but there is even more work ahead than initially. In the end, being able to create your own object with a more limited interface and private vector might be safer.

Threat 2: more inconsistency

Vector indexes vector<T>::size_type

. Unfortunately this type is unsigned. The impact of inheritance on a vector but overridden operator[]

with signed integer indices needs to be carefully considered. This can lead to subtle errors depending on the definition of the indexes.

Conclusions:

There's more work out there that you think the serial interface offers std::vector

. So in the end, using your own class using a private vector may be safer.

You should also consider that your code will one day be maintained by people without a fortran background, and they may have wrong assumptions about []

your code. Does the question really come from native C ++?

+2


source


It seems bad to stick to composition and write wrappers for the member functions you need. There are not many of them. I array

might even be tempted to make a public item public so you can access it directly when needed, although some people think that there is no more no more than inheriting from a base class without a virtual destructor.

template <typename T, size_t N, int start>
class customArray
{
public:
    std::array<T,N> data;

    T& operator[](int idx) { return data[idx+start]; }
    auto begin() { return data.begin(); }
    auto begin() const { return data.begin(); }
    auto end() { return data.end(); }
    auto end() const { return data.end(); }
    auto size() const { return data.size(); }
};

int main() {
    customArray<int, 7, -3> a;
    a.data.fill(5);  // can go through the `data` member...
    for (int& i : a) // ...or the wrapper functions (begin/end).
        cout << i << endl;
}

      

0


source







All Articles