Home > OS >  I am trying to allocate as less memory as possible to better understand the move semantics in c
I am trying to allocate as less memory as possible to better understand the move semantics in c

Time:07-12

I have written this piece of code for better understanding the move semantic:

Main:

#include <iostream>
#include "Var.h"

struct AllocationMetrics {
    uint32_t totalAllocated = 0;
    uint32_t totalFreed = 0;

    uint32_t CurrentUsage() {
        return totalAllocated - totalFreed;
    }
};

static AllocationMetrics allocationMetrics;

void *operator new(size_t size) {
    allocationMetrics.totalAllocated  = size;
    return malloc(size);
}

void operator delete(void *memory, size_t size) {
    allocationMetrics.totalFreed  = size;
    return free(memory);
}

static void printMemoryUsage () {
    std::cout << "Current memory usage (bytes): " << allocationMetrics.CurrentUsage() << std::endl;
}

int main() {
    printMemoryUsage();
    Var v1{"0011223344556677889"};
    printMemoryUsage();

    Var v2 = std::move(v1);
    printMemoryUsage();

    v2 = "yyyyyyyyyyyyyyyyyyy";
    printMemoryUsage();

}

Class Var:

class Var {
private:
    std::string varName;

public:
    explicit Var(std::string _varName) : varName(std::move(_varName)) {
        std::cout << "ctor\n" << &varName << std::endl;
    } //ctor

    Var(const Var &otherVar) = delete;

    Var(Var &&otherVar) noexcept : varName{std::move(otherVar.varName)} { 
       std::cout << "mtor" << std::endl; 
    }

    Var& operator=(const std::string& var) {
        std::cout << "copy\n";
        varName = var;
        return *this;
    }

    Var& operator=(std::string&& var) {
        std::cout << "move\n";

        varName = std::move(var);
        return *this;
    }

    virtual ~Var() { std::cout << "dtor" << std::endl; };

    bool operator==(const Var &rhs) const {
        return varName == rhs.varName;
    }

    bool operator!=(const Var &rhs) const {
        return !(rhs == *this);
    }

    friend std::ostream &operator<<(std::ostream &os, const Var &var) {
        os << "varName: " << var.varName;
        return os;
    }

};

I have a class "Var" with one field of std::string type. I construct a Var v1 and then a Var v2 using the move constructor on v1, and that work great because the memory allocation remains the same.

Then I want to assign a new varName to v2 (v2 = "yyyyyyyyyyyyyyyyyyy"). Since I have allocated a new constant string, the total memory allocation increases. But then there is an assignment to the varName field (in method Var& operator=(std::string&& var)) so I would expect that the previously allocated memory that holds "0011223344556677889" in varName were freed, and then varName were assigned to the newly allocated memory block.

So in other word, how can I free the memory allocated for the varName field and allocate a new memory block to be pointed by varName? Am I using the move semantic correctly?

CodePudding user response:

v2 = "yyyyyyyyyyyyyyyyyyy". I think this might be the first cause for your misunderstanding. A string literal has type const char[N], not std::string.

Anyway, there is no guarantee that std::string::operator= allocates a new memory block. When the existing allocation is large enough, it's simpler and faster to reuse that. So, your question : "how can I free the memory allocated for the varName field and allocate a new memory block to be pointed by varName?" has no simple answer.

In theory, you could call .clear() followed by .shrink_to-fit() but even that is non-binding. There's just no good reason in normal C programs to do what you want, so there is no method in std::string for that.

CodePudding user response:

I've added debug output in the new and delete overloads: https://godbolt.org/z/Tn8EYfxPx

With that I get the following output:

Current memory usage (bytes): 0
allocate 20                        <--- Var v1{"0011223344556677889"};
ctor
0x7fff040c9d88
Current memory usage (bytes): 20
mtor                               <--- Var v2 = std::move(v1);
Current memory usage (bytes): 20
allocate 20                        <--- v2 = "yyyyyyyyyyyyyyyyyyy";
move
delete 20
Current memory usage (bytes): 20
dtor
delete 20
dtor

As you can see when you write v2 = "yyyyyyyyyyyyyyyyyyy"; the compiler invokes Var& operator=(std::string&& var) {. For this the C string is first converted to std::string causing the allocation. Then the move happens and swaps the 2 strings around. Last the temporary string is destructed causing the delete of the old contents of v2.

Note: the move could also delete the old contents directly but I think it swaps contents as that is faster in some cases.

PS: You could add a assignment operator using string_view that resizes the string and copies the contents. This would avoid the allocation and deallocation for the std::string temporary.

  •  Tags:  
  • c
  • Related