2

I am trying to concatenate two char arrays char a[9000]; char b[9000] to a container std::vector<char>. Since the array size is not small. I am trying to use std::move()

class obj
{
public:
obj &operator <<(char *&&ptr)
        {
            //???
            ptr = nullptr;
            return *this;
        }
private:
    std::vector<char> m_obj;
};

So in the main function

int main()
{
    obj o;
    enum {SIZE=9000};
    char a[SIZE]; memset(a, 1, SIZE);
    char b[SIZE]; memset(b, 2, SIZE);
    o << a << b;
    return 0;
}

My question is what is the correct way to move a char*?


Environment:
Ubuntu 16.04.4 LTS
g++ 5.4.0

Donald Duck
  • 8,409
  • 22
  • 75
  • 99
r0n9
  • 2,505
  • 1
  • 29
  • 43
  • 2
    A `char*` is a trivial object (it's typically 8 bytes). You won't gain anything from moving it. – Quentin Aug 10 '17 at 14:14
  • Simple answer: don't, use a `std::string` instead. – NathanOliver Aug 10 '17 at 14:15
  • 1
    `move` doesn't actually move anything. And 9000 `char`s is a very small amount that's nothing to worry about. – molbdnilo Aug 10 '17 at 14:20
  • 2
    You can't move a stack-allocated array into a heap object. That makes no sense. (What would you expect to happen when the stack-allocated array goes out of scope?) – cdhowie Aug 10 '17 at 14:44
  • 1
    You cannot move a `char*` to a `char[]` or to a `vector`, they are completely diffetent things. Moving primitive data types is exactly the same as copying. You can use `memcpy` or `memmove` to copy characters from one array to another, though generally you shouldn't. Use std::string instead. – n. m. could be an AI Aug 10 '17 at 14:55

4 Answers4

6

tl;dr

Call reserve() and copy.

What's wrong with moving?

Several things. First, the expectation that you seem to have about std::move is wrong. std::move does not magically move anything (or moves anything at all, including non-magically). What it does is a cast to a rvalue reference, which may enable moving under some conditions.

Second, beginning with C++03, std::vector is guaranteed to have its element contiguously laid out in memory (before C++03 it was factually the case anyway, but no explicit guarantee was given). If you use std::move then you necessarily use a version later than C++03, so std::vector necessarily lays out its elements contiguously.
Which means that except for the totally coincidal case that the two arrays happen to be adjacent to each other, there is no way of getting the elements from two arrays into a vector without copying. It's simply not possible to move them because at least one array must be relocated (= copied).

Further, I couldn't imagine how moving elements from an array the way you intend to do it would be possible in the first place. Moving assumes that you take over ownership, which usually means you modify the moved-from object in some way so that it is still valid, but without whatever resources it owned previously.
How do you do that with an array that has automatic storage duration in a surrounding scope? What is dereferencing the array after the move (which is possible and legitimate) going to do then?

Also, you want to decay an array to a pointer (that's perfectly legal) and then somehow magically move. That, however, does not move the contents of the array! The best you can do is move the pointer, but that doesn't make sense since moving a pointer is just the same as copying it.

There is no way you're getting around having to copy the array elements, but you can save one memory allocation and one needless copy by properly calling reserve() ahead of time.

Community
  • 1
  • 1
Damon
  • 67,688
  • 20
  • 135
  • 185
1

Well, char* isn't exactly object you want to move. So, getting rvalue reference to the pointer won't exactly help. Why don't you try array-like way of moving and use std::move from "algorithm"?

http://en.cppreference.com/w/cpp/algorithm/move

It'd look something like that:

obj& obj::addChars(char *ptr, size_t size = 9000u) {    
    m_obj.resize(size);
    std::move(ptr, ptr + size, m_obj.begin());
    return *this;
}
John Cvelth
  • 522
  • 1
  • 6
  • 19
  • 1
    `m_obj` needs to be appropriately sized for this to work. Might want to use a `back_inserter` instead. – NathanOliver Aug 10 '17 at 14:18
  • @NathanOliver I'm not sure about it, but it seems like resizing vector once(if you know the number of elements you need) is better then putting elements one by one, because then there are no need of moving data once its to big to already allocated inner array. But I can be mistaking inner structure of std::vector. – John Cvelth Aug 10 '17 at 14:23
  • That's an illegal signature for `operator<<` (only allowed one argument). – Galik Aug 10 '17 at 14:36
  • "An object is a region of storage" (The Standard). – n. m. could be an AI Aug 10 '17 at 14:56
  • 2
    @John you can use `reserve` first – n. m. could be an AI Aug 10 '17 at 14:57
  • @n.m. and `char*` is a pointer to the first `object` of type `char` in given array. – John Cvelth Aug 10 '17 at 14:58
  • Yes, it is also an object. – n. m. could be an AI Aug 10 '17 at 14:59
  • @n.m. what is the difference between `resize` + `move to begin` and `reserve` + `move each element to the back (using iterator)` ? – John Cvelth Aug 10 '17 at 15:00
  • @n.m. all right, it is, but I don't see why would somebody try moving object, storing address of other one. It's 4/8 bytes, anyway. Even 1000 copies won't harm. – John Cvelth Aug 10 '17 at 15:01
  • @JohnCvelth moving an object is a way of copying it. Moving an object of fundamental type is exactly the same as copying. So, moving a `char*` is copying. Just like moving a `char` is copying. I would use `std::copy` here to not confuse the reader of the code. – eerorika Aug 10 '17 at 15:27
  • @user2079303 using `std::move` you explicitly allow to do what-ever-is-better-for-quicker-copying with the data (including corrupting). That's the fundamental difference between `copy` and `move`. Who know what effect it will really have but it won't make matters worse, will it? – John Cvelth Aug 10 '17 at 15:34
1

The following works because a vector is contiguous memory. It is necessary to resize the vector to hold the amount of elements being copied.

&vc[0] is the memory address of the 0th element of the vector. &vc[SIZE] is the memory address of the 9000th element of the vector, which is where array b should start.

This will execute much faster than trying to iterate through the arrays and push_back, or assign the vector elements.

enum {SIZE=9000};
char a[SIZE];
memset(a, 1, SIZE);

char b[SIZE];
memset(b, 2, SIZE);

// make a vector of characters
std::vector<char> vc;
// resize to hold two arrays
vc.resize(SIZE * 2);

// copy array a to the beginning of the vector
memcpy(&vc[0], a, SIZE);
// copy array b to the end of the vector
memcpy(&vc[SIZE], b, SIZE);

edit:

std::vector<char> vc(SIZE * 2);

is the same as create, then resize. It will create the vector with the specified size, then the resize is not necessary.

ttemple
  • 852
  • 5
  • 20
0

Even if you allocated the arrays using new there is no way to get vector to adopt the arrays. A copy will take place.

The recommended approach is std::copy which in most implementations will be recognized as optimizable to a std::memcpy().

#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>

#define SIZE 90

int main(void) {
    std::vector<char> v;
    v.resize(2*SIZE);

    char a[SIZE]; 
    char b[SIZE]; 

    std::memset(a, 'a', SIZE);
    std::memset(b, 'b', SIZE);

    std::copy(a,a+SIZE,v.begin());
    std::copy(b,b+SIZE,v.begin()+SIZE);

    for(char c : v){
        std::cout << c;
    }
    std::cout << std::endl;
    return 0;
}

If it isn't so optimized (unlikely), use a std::memcpy().

How you implement that in you class is another debate. You could implement an add(char *start,char *end); member.

As others point out 9000 char isn't huge and you might be over-engineering if you do more.

A further extension would allow you to resize() or reserve() size in the vector to avoid an inevitable resize during the second copy.

Persixty
  • 8,165
  • 2
  • 13
  • 35