2

我正在编写一个自定义内存分配器。如果可能的话,我想让这样的对象创建功能完全抽象创建过程。

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

但这会导致复制。在这种情况下,有没有办法强制消除复制?

这是我当前的测试代码。

#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;
}

我试图通过valueas T&, T const&, T&&, T const&&,但它仍然复制。我预计优化器会消除函数框架,因此可以将函数参数推导出为 R 值,但我仍然看到COPY!消息。

我也尝试过 C++11 转发模板,但我不能使用它,因为它不能是一个虚函数。

我的编译器是 Xcode 中包含的 Clang。(clang-425.0.28) 优化级别设置为-Os -flto.

更新(供以后参考)

clang -Os -flto -S -emit-llvm -std=c++11 -stdlib=libc++ main.cpp;我编写了额外的测试,并使用选项检查了生成的 LLVM IR 。我能观察到的是:(1)功能框架总是可以被消除的。(2)如果大对象(4096字节)按值传递,它没有被淘汰。(3) 通过 r 值引用 usingstd::move效果很好。(4) 如果我不制作移动构造函数,编译器大多会退回到复制构造函数。

4

3 回答 3

2

第一:
您希望优化器消除函数框架,但它不能,因为(1)该函数不是 RVO 作弊并完全跳过复制构造函数的有效案例,以及(2)您的函数有副作用. 即,写入copy屏幕。所以优化器不能优化出副本,因为代码说它必须写出copy两次。我要做的是删除printf呼叫,并检查程序集。最有可能的是,如果它很简单,它正在被优化。

第二:
如果成员复制构造函数可能有副作用(例如分配内存),那么您很可能是对的,它没有被优化。但是,您可以告诉它移动成员而不是复制它们,这可能是您所期望的。为此,您需要确保您的类在复制构造函数旁边有一个移动构造函数。

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

第三:
实际上,即使那也不是您真正想要的。您可能想要的是更像完美转发的东西,它将直接将参数传递给构造函数,而无需复制任何内容。这意味着即使完全禁用优化器,您的代码仍会避免复制。(请注意,这可能不适用于您的情况,模板不能virtual这样,除非它是专门的)

template<class ...types>
inline static void test(types&&... values) __attribute__((always_inline))
{
    AA* ptr =   new AA(std::forward<types>(values)...);
    delete ptr;
}
于 2013-05-08T01:08:37.573 回答
2

您的复制构造函数不是在行中显式调用AA* ptr = new AA(aa);吗?如果你想避免复制初始化值,你应该这样做:

#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;
}

问题:

  • 允许默认移动构造函数的条件非常严格,您最好自己编写一个。
  • 如果您传递值(即使是临时值),您仍然需要 std::move 来进一步传递它而不创建副本。
于 2013-05-08T01:09:27.890 回答
0

首先,让我们解决您的A&&移动问题。您可以通过创建一个移动构造函数(或将其声明为默认构造函数)(或让编译器生成一个)来获取一个移动构造函数,并确保std::moveaa传递它时调用它:Live example here

这同样适用于您正在使用的任何虚拟实例功能。我还在示例中包含了转发可变参数版本,以防万一您也想要有关如何执行此操作的参考(请注意所有参数都被std::forward编入下一次调用)。

在任何一种情况下,这似乎都是从一个严重的案例中产生的问题XY,即:您正在尝试解决您使用当前设计创建的问题。我不知道为什么需要通过实例方法在预先存在的对象上创建对象(工厂类型创建?),但这听起来很混乱。无论哪种情况,以上内容都应该可以帮助您继续前进。只要确保您正在移动参数,总是&&用 a 包裹项目std::move(或者std::forward如果您正在使用通用引用和模板参数)。

于 2013-05-08T01:07:55.260 回答