Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't we change data pointer of std::vector?

I have an array char* source and a vector std::vector<char> target. I'd like to make the vector target point to source in O(1), without copying the data.

Something along these lines:

#include <vector>

char* source = new char[3] { 1, 2, 3 };
std::vector<char> target;
target.resize(3);

target.setData(source); // <- Doesn't exist
// OR
std::swap(target.data(), source); // <- swap() does not support char*
delete[] source;

Why is it not possible to manually change where a vector points to? Is there some specific, unmanageable problem that would arise if this was possible?

like image 414
Antti_M Avatar asked Dec 30 '20 10:12

Antti_M


People also ask

Can a vector be a pointer C++?

An ordinary vector encountered in C++ programming, is a vector of objects of the same type. These objects can be fundamental objects or objects instantiated from a class. This article illustrates examples of vector of pointers, to same object type.

Can we use pointers for vectors?

You can store pointers in a vector just like you would anything else. Declare a vector of pointers like this: vector<MyClass*> vec; The important thing to remember is that a vector stores values without regard for what those values represent.

Can vector have different data types C++?

C++ is a statically-typed language. A vector will hold an object of a single type, and only a single type.

How do you modify a value in a vector?

The syntax for modifying values from a vector vectorname. assign(InputIterator first, InputIterator last) Parameters: first - Input iterator to the initial position range. last - Input iterator to the final position range.


2 Answers

C++ vector class supports adding and deleting elements, with guaranteed consecutive order in memory. If you could initialize your vector with existing memory buffer, and add enough elements to it, it would either overflow or require reallocation.

The interface of vector assumes that it manages its internal buffer, that is, it can allocate, deallocate, resize it whenever it wants (within spec, of course). If you need something that is not allowed to manage its buffer, you cannot use vector - use a different data structure or write one yourself.

You can create a vector object by copying your data (using a constructor with two pointers or assign), but this is obviously not what you want.

Alternatively, you can use string_view, which looks almost or maybe exactly what you need.

like image 112
anatolyg Avatar answered Oct 06 '22 01:10

anatolyg


std::vector is considered to be the owner of the underlying buffer. You can change the buffer but this change causes allocation i.e. making a copy of the source buffer which you don't want (as stated in the question).

You could do the following:


#include <vector>

int main() {
    char* source = new char[3] { 1, 2, 3 };
    std::vector<char> target;
    target.resize(3);
    target.assign(source, source + 3);
    delete[] source;
    return 0;
}

but again std::vector::assign:

Replaces the contents with copies of those in the range [first, last).

So copy is performed again. You can't get away from it while using std::vector.

If you don't want to copy data, then you should use std::span from C++20 (or create your own span) or use std::string_view (which looks suitable for you since you have an array of chars).

1st option: Using std::string_view

Since you are limited to C++17, std::string_view might be perfect for you. It constructs a view of the first 3 characters of the character array starting with the element pointed by source.

#include <iostream>
#include <string_view>

int main() {
    char* source = new char[3] { 1, 2, 3 };

    std::string_view strv( source, 3 );

    delete[] source;

    return 0;
}

2nd option: Using std::span from C++20

std::span comes from C++20 so it might not be the most perfect way for you, but you might be interested in what it is and how it works. You can think of std::span as a bit generalized version of std::string_view because it is a contiguous sequence of objects of any type, not just characters. The usage is similar as with the std::string_view:

#include <span>
#include <iostream>

int main() {
    char* source = new char[3] { 1, 2, 3 };

    std::span s( source, 3 );

    delete[] source;

    return 0;
}

3rd option: Your own span

If you are limited to C++17, you can think of creating your own span struct. It might still be an overkill but let me show you (btw take a look at this more elaborated answer):

template<typename T>
class span {
   T* ptr_;
   std::size_t len_;

public:
    span(T* ptr, std::size_t len) noexcept
        : ptr_{ptr}, len_{len}
    {}

    T& operator[](int i) noexcept {
        return *ptr_[i];
    }

    T const& operator[](int i) const noexcept {
        return *ptr_[i];
    }

    std::size_t size() const noexcept {
        return len_;
    }

    T* begin() noexcept {
        return ptr_;
    }

    T* end() noexcept {
        return ptr_ + len_;
    }
};

int main() {
    char* source = new char[3] { 1, 2, 3 };

    span s( source, 3 );

    delete[] source;

    return 0;
}

So the usage is the same as with the C++20's version of std::span.

like image 20
NutCracker Avatar answered Oct 06 '22 00:10

NutCracker