Wrap existing memory with const std::vector?

2020-07-02 19:54发布

问题:

OK, so I recently learned that (a) std::vector uses contiguous memory by definition/standard, and thus (b) &(v[0]) is the address of that contiguous block of memory, which you can read/write to as an old-skool C-array. Like...

void printem(size_t n, int* iary)
{ for (size_t i=0; i<n; ++i) std::cout << iary[i] << std::endl; }
void doublem(size_t n, int* iary)
{ for (size_t i=0; i<n; ++i) iary[i] *= 2; }

std::vector<int> v;
for (size_t i=0; i<100; ++i) v.push_back(i);
int* iptr = &(v[0]);
doublem(v.size(), iptr);
printem(v.size(), iptr);

OK, so that's cool, but I want to go in the other direction. I have lots and lots of existing code like

double computeSomething(const std::vector<SomeClass>& v) { ... }

If I have a C-array of objects, I can use such code like this:

SomeClass cary[100]; // 100*sizeof(SomeClass)
// populate this however
std::vector<SomeClass> v;
for (size_t i=0; i<100; ++i) v.push_back(cary[i]);
// now v is also using 100*sizeof(SomeClass)
double x = computeSomething(v);

I would like to do that (a) without the extra space and (b) without the extra time of inserting a redundant copy of all that data into the vector. Note that "just change your stupid computeSomething, idiot" is not sufficient, because there are thousands of such functions/methods that exhibit this pattern that are not under my control and, even if they were are too many to go and change all of them.

Note also that because I am only interested in const std::vector& usage, there is no worry that my original memory will ever need to be resized, or even modified. I would want something like a const std::vector constructor, but I don't know if the language even allows special constructors for const instances of a class, like:

namespace std { template <typename T> class vector {
  vector() { ... }
  vector(size_t n) { ... }
  vector(size_t n, const T& t) { ... }
  const vector(size_t n, T*) { ... } // can this be done?
...

If that is not possible, how about a container derived off of std::vector called std::const_vector, which (a) could construct from a pointer to a c-array and a size, and (b) purposefully did not implement non-const methods (push_back, resize, etc.), so then even if the object with a typename of const_vector is not actually a const object, the interface which only offers const methods makes it practically const (and any erroneous attempts to modify would be caught at compile time)?

UPDATE: A little messing around shows that this "solves" my problem wrt Windows-implementation of std::vector:

template <typename T>
class vector_tweaker : public std::vector<T> {
public:
  vector_tweaker(size_t n, T* t) {
    _saveMyfirst = _Myfirst;
    _saveMylast  = _Mylast;
    _saveMyend   = _Myend;
    _Myfirst = t;
    _Mylast  = t + n;
    _Myend   = t + n;
  }
  ~vector_tweaker() {
    _Myfirst = _saveMyfirst;
    _Mylast  = _saveMylast;
    _Myend   = _saveMyend; // and proceed to std::vector destructor
  }
private:
  T* _saveMyfirst;
  T* _saveMylast;
  T* _saveMyend;
};

But of course that "solution" is hideous because (a) it offers no protection against the base class deleting the original memory by doing a resize() or push_back() (except for a careful user that only constructs const vector_tweaker()) -- and (b) it is specific to a particular implementation of std::vector, and would have to be reimplemented for others -- if indeed other platforms only declare their std::vector member data as protected: as microsoft did (seems a Bad Idea).

回答1:

You can try reference-logic storing introduced in C++11 with std::reference_wrapper<>:

SomeClass cary[100];
// ...
std::vector<std::reference_wrapper<SomeClass>> cv;
cv.push_back(cary[i]);   // no object copying is done, reference wrapper is stored

Or without C11, you can create a specialization of such template class for bytes - char. Then for the constructor from char* C-array you can use ::memcpy: which unfortunately will then use twice as much memory.

::memcpy(&v[0], c_arr, n);

Something like this:

template <typename T> class MyVector : public std::vector<T> {
};

template <> class MyVector<char> : public std::vector<char> {
    public:
    MyVector<char>(char* carr, size_t n) : std::vector<char>(n) {
        ::memcpy(&operator[](0), carr, n);
    }
};

What I would recommend - replace all C-arrays to vectors where possible, then no extra copying will be needed.