Pergunta

I am writing a custom memory allocator. If possible, I want to make object creation function like this to abstract creation procedure completely.

template<typename T>
class CustomCreator
{
    virtual T& createObject(T value) __attribute__((always_inline))
    { 
        T* ptr = (T*)customAlloc();
        new (ptr) T(value);
        return *ptr;
    }
}

But this causes copying. Is there a way to force to eliminate copying in this case?

Here's my current testing code.

#include <iostream>

struct AA
{
    inline static void test(AA aa) __attribute__((always_inline))
    {
        AA* ptr =   new AA(aa);
        delete ptr;
    }

    AA(AA const& aa)
    {
        printf("COPY!\n");
    }
    AA(int v)
    {
        printf("CTOR!\n");
    }
};

int main(int argc, const char * argv[])
{
    AA::test(AA(77));
    return 0;
}

I tried to pass the value as T&, T const&, T&&, T const&&, but it still copies. I expected optimizer will eliminate function frame, so the function parameter can be deduced into an R-value, but I still see COPY! message.

I also tried C++11 forwarding template, but I couldn't use it because it cannot be a virtual function.

My compiler is Clang included in Xcode. (clang-425.0.28) And optimization level is set to -Os -flto.

Update (for later reference)

I wrote extra tests, and checked generated LLVM IR with clang -Os -flto -S -emit-llvm -std=c++11 -stdlib=libc++ main.cpp; options. What I could observe are: (1) function frames always can be get eliminated. (2) if large object (4096 byte) passed by value, it didn't get eliminated. (3) Passing r-value reference using std::move works well. (4) if I don't make move-constructor, compiler mostly fall back to copy-constructor.

Foi útil?

Solução

First:
You expected the optimizer to eliminate the function frame, but it can't, because (1) that function isn't a valid case for RVO to cheat and skip the copy constructor entirely, and (2) Your function has side effects. Namely, writing copy to the screen. So the optimizer can't optimize out the copy, because the code says it must write copy out twice. What I would do is remove the printf call, and check the assembly. Most likely, if it's simple, it is being optimized out.

Second:
If the members copy constructors can have side effects (such as allocating memory), then most likely you're right that it isn't being optimized out. However, you can tell it to move members rather than copying them, which is probably what you expected. For this to work you'll need to make sure your class has a move constructor alongside your copy constructor.

inline static void test(AA aa) __attribute__((always_inline))
{
    AA* ptr =   new AA(std::move(aa));
    delete ptr;
}

Third:
In reality, even that isn't really what you want. What you probably want is something more like perfect forwarding, which will just directly pass the parameters to the constructor without making copies of ANYTHING. This means even with the optimizer disabled entirely, your code is still avoiding copies. (Note that this may not apply to your situation, templates cannot be virtual like this, unless it's specialized)

template<class ...types>
inline static void test(types&&... values) __attribute__((always_inline))
{
    AA* ptr =   new AA(std::forward<types>(values)...);
    delete ptr;
}

Outras dicas

Isn't your copy-constructor explicitly called in line AA* ptr = new AA(aa);? If you want to avoid copying initializing value, you should do it like this:

#include <iostream>
#include <utility>

int main(int argc, const char * argv[])
{
    struct
    AA
    {   inline static void test(AA aa) __attribute__((always_inline))
        {
            AA* ptr = new AA(std::move(aa));
            delete ptr;
        }

        inline static void test(AA&& aa) __attribute__((always_inline))
        {
            AA* ptr = new AA(std::move(aa));
            delete ptr;
        }

        AA(AA const& aa)
        {
            printf("COPY!\n");
        }

        AA(AA const&& aa) // Move constructors are not default here - you have to declare one
        {
            printf("MOVE!\n");
        }

        AA(int v)
        {
            printf("CTOR!\n");
            xx  =   v;
        }

        int xx  =   55;
    };

    AA::test(AA(77));
    return 0;
}

Problems:

  • Conditions allowing default move constructor are quite tight, you are better off writing one yourself.
  • If you pass value along (even a temporary one) you still need std::move to to pass it even further without creating a copy.

First, let's solve your A&& move problem. You can get a move constructor by making a move constructor (or declaring it default) (or letting the compiler generate one), and making sure you call std::move on aa when you pass it through: Live example here.

The same will apply to any virtual instance function you are working with. I've also included the forwarding variadic version in the example as well, just in case you'd like a reference on how to do that as well (notice all the arguments are std::forwarded to their next calls).

In either case, this seems like a question spawned from a serious case of XY, that is: you're trying to solve a problem you've created with your current design. I don't know why you need to create objects through instance methods on a pre-existing object (factory-type creation?), but it sounds messy. In either case, the above should help you out enough to get your going. Just make sure if you're moving parameters, to always wrap the && item with a std::move (or std::forward if you're working with universal references and template parameters).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top