有没有办法在不使用new的情况下从函数返回抽象(出于性能原因)

sji*_*sji 23 c++ oop

例如,我有一些pet_maker()创建并返回a Cat或a Dog作为基础的函数Pet.我想多次调用这个函数,并对Pet返回的函数做一些事情.

传统上我newCat还是Dogpet_maker()和返回一个指向它,但是new通话比在栈上所做的一切要慢得多.

是否有一种简洁的方式,任何人都可以想到作为抽象返回,而不必每次调用函数时都做新的,或者是否有其他方法可以快速创建和返回抽象?

Arm*_*yan 20

如果你想要多态,那么使用new几乎是不可避免的.但新工作缓慢的原因是因为它每次都在寻找免费记忆.您可以做的是编写自己的运算符new,理论上可以使用预先分配的内存块并且速度非常快.

本文介绍了您可能需要的许多方面.

  • 只是提示:根据时间戳,您在发布问题后几乎立即接受了答复.你可能想等一下接受,可能会弹出更好的答案(即另一个答案中来自@Galik的批量分配方法).我不知道你究竟打算实现什么(肯定不是字面意思"猫"和"狗"......); 并且在大多数情况下,最好先尝试一些高级优化,然后再将特殊内存分配到特殊内存分配(你必须维护和调试代码,也可能在3年之后......). (8认同)
  • 对我来说,这似乎是唯一合适的解决方案.您还可以查看"飞机"示例中的meyers有效c ++,这可能比链接页面中显示的更简单. (4认同)
  • 将缓存/池引入`pet_maker()`可能比编写自己的`operator new`更有意义......我想,取决于实际的用例. (3认同)

Gal*_*lik 10

每个分配都是一个开销,因此您可以通过分配整个对象数组而不是一次分配一个对象来获得好处.

您可以使用std :: deque来实现此目的:

class Pet { public: virtual ~Pet() {} virtual std::string talk() const = 0; };
class Cat: public Pet { std::string talk() const override { return "meow"; }};
class Dog: public Pet { std::string talk() const override { return "woof"; }};
class Pig: public Pet { std::string talk() const override { return "oink"; }};

class PetMaker
{
    // std::deque never re-allocates when adding
    // elements which is important when distributing
    // pointers to the elements
    std::deque<Cat> cats;
    std::deque<Dog> dogs;
    std::deque<Pig> pigs;

public:

    Pet* make()
    {
        switch(std::rand() % 3)
        {
            case 0:
                cats.emplace_back();
                return &cats.back();
            case 1:
                dogs.emplace_back();
                return &dogs.back();
        }
        pigs.emplace_back();
        return &pigs.back();
    }
};

int main()
{
    std::srand(std::time(0));

    PetMaker maker;

    std::vector<Pet*> pets;

    for(auto i = 0; i < 100; ++i)
        pets.push_back(maker.make());

    for(auto pet: pets)
        std::cout << pet->talk() << '\n';
}
Run Code Online (Sandbox Code Playgroud)

使用std :: deque的原因是它在添加新元素从不重新分配其元素,因此您分发的指针始终保持有效,直到PetMaker删除它为止.

与单独分配对象相比,这样做的另一个好处是它们不需要删除或放在智能指针中,std :: deque管理它们的生命周期.


Mat*_* M. 10

是否有一种简洁的方式,任何人都可以想到作为抽象返回,而不必new每次调用函数时都这样做,或者是否有其他方法可以快速创建和返回抽象?

TL; DR:如果已经有足够的内存可以使用,则无需分配函数.

一种简单的方法是创建一个与其兄弟姐妹略有不同的智能指针:它将包含一个缓冲区,用于存储该对象.我们甚至可以让它不可空!


长版:

我将以相反的顺序呈现草稿,从动机到棘手的细节:

class Pet {
public:
    virtual ~Pet() {}

    virtual void say() = 0;
};

class Cat: public Pet {
public:
    virtual void say() override { std::cout << "Miaou\n"; }
};

class Dog: public Pet {
public:
    virtual void say() override { std::cout << "Woof\n"; }
};

template <>
struct polymorphic_value_memory<Pet> {
    static size_t const capacity = sizeof(Dog);
    static size_t const alignment = alignof(Dog);
};

typedef polymorphic_value<Pet> any_pet;

any_pet pet_factory(std::string const& name) {
    if (name == "Cat") { return any_pet::build<Cat>(); }
    if (name == "Dog") { return any_pet::build<Dog>(); }

    throw std::runtime_error("Unknown pet name");
}

int main() {
    any_pet pet = pet_factory("Cat");
    pet->say();
    pet = pet_factory("Dog");
    pet->say();
    pet = pet_factory("Cat");
    pet->say();
}
Run Code Online (Sandbox Code Playgroud)

预期产量:

Miaou
Woof
Miaou
Run Code Online (Sandbox Code Playgroud)

你可以在这里找到.

请注意,需要指定可支持的派生值的最大大小和对齐方式.没办法解决这个问题.

当然,我们会静态检查调用者是否会尝试使用不合适的类型来构建值,以避免任何不愉快.

当然,主要的缺点是它必须至少与其最大的变体一样大(并且对齐),并且所有这些必须提前预测.因此,这不是灵丹妙药,但在性能方面,缺乏记忆分配可能会撼动.


它是如何工作的?使用这个高级类(和帮助器):

//  To be specialized for each base class:
//  - provide capacity member (size_t)
//  - provide alignment member (size_t)
template <typename> struct polymorphic_value_memory;

template <typename T,
          typename CA = CopyAssignableTag,
          typename CC = CopyConstructibleTag,
          typename MA = MoveAssignableTag,
          typename MC = MoveConstructibleTag>
class polymorphic_value {
    static size_t const capacity = polymorphic_value_memory<T>::capacity;
    static size_t const alignment = polymorphic_value_memory<T>::alignment;

    static bool const move_constructible = std::is_same<MC, MoveConstructibleTag>::value;
    static bool const move_assignable = std::is_same<MA, MoveAssignableTag>::value;
    static bool const copy_constructible = std::is_same<CC, CopyConstructibleTag>::value;
    static bool const copy_assignable = std::is_same<CA, CopyAssignableTag>::value;

    typedef typename std::aligned_storage<capacity, alignment>::type storage_type;

public:
    template <typename U, typename... Args>
    static polymorphic_value build(Args&&... args) {
        static_assert(
            sizeof(U) <= capacity,
            "Cannot host such a large type."
        );

        static_assert(
            alignof(U) <= alignment,
            "Cannot host such a largely aligned type."
        );

        polymorphic_value result{NoneTag{}};
        result.m_vtable = &build_vtable<T, U, MC, CC, MA, CA>();
        new (result.get_ptr()) U(std::forward<Args>(args)...);
        return result;
    }

    polymorphic_value(polymorphic_value&& other): m_vtable(other.m_vtable), m_storage() {
        static_assert(
            move_constructible,
            "Cannot move construct this value."
        );

        (*m_vtable->move_construct)(&other.m_storage, &m_storage);

        m_vtable = other.m_vtable;
    }

    polymorphic_value& operator=(polymorphic_value&& other) {
        static_assert(
            move_assignable || move_constructible,
            "Cannot move assign this value."
        );

        if (move_assignable && m_vtable == other.m_vtable)
        {
            (*m_vtable->move_assign)(&other.m_storage, &m_storage);
        }
        else
        {
            (*m_vtable->destroy)(&m_storage);

            m_vtable = other.m_vtable;
            (*m_vtable->move_construct)(&other.m_storage, &m_storage);
        }

        return *this;
    }

    polymorphic_value(polymorphic_value const& other): m_vtable(other.m_vtable), m_storage() {
        static_assert(
            copy_constructible,
            "Cannot copy construct this value."
        );

        (*m_vtable->copy_construct)(&other.m_storage, &m_storage);
    }

    polymorphic_value& operator=(polymorphic_value const& other) {
        static_assert(
            copy_assignable || (copy_constructible && move_constructible),
            "Cannot copy assign this value."
        );

        if (copy_assignable && m_vtable == other.m_vtable)
        {
            (*m_vtable->copy_assign)(&other.m_storage, &m_storage);
            return *this;
        }

        //  Exception safety
        storage_type tmp;
        (*other.m_vtable->copy_construct)(&other.m_storage, &tmp);

        if (move_assignable && m_vtable == other.m_vtable)
        {
            (*m_vtable->move_assign)(&tmp, &m_storage);
        }
        else
        {
            (*m_vtable->destroy)(&m_storage);

            m_vtable = other.m_vtable;
            (*m_vtable->move_construct)(&tmp, &m_storage);
        }

        return *this;
    }

    ~polymorphic_value() { (*m_vtable->destroy)(&m_storage); }

    T& get() { return *this->get_ptr(); }
    T const& get() const { return *this->get_ptr(); }

    T* operator->() { return this->get_ptr(); }
    T const* operator->() const { return this->get_ptr(); }

    T& operator*() { return this->get(); }
    T const& operator*() const { return this->get(); }

private:
    polymorphic_value(NoneTag): m_vtable(0), m_storage() {}

    T* get_ptr() { return reinterpret_cast<T*>(&m_storage); }
    T const* get_ptr() const { return reinterpret_cast<T const*>(&m_storage); }

    polymorphic_value_vtable const* m_vtable;
    storage_type m_storage;
}; // class polymorphic_value
Run Code Online (Sandbox Code Playgroud)

从本质上讲,这就像任何STL容器一样.大部分复杂性在于重新定义构造,移动,复制和销毁.否则很简单.

有两点值得注意:

  1. 我使用基于标签的方法来处理功能:

    • 例如,复制构造函数仅在CopyConstructibleTag传递时可用
    • 如果CopyConstructibleTag传递,则传递给的所有类型build 必须是可复制的
  2. 即使对象不具备该能力,也提供一些操作,只要存在提供它们的一些替代方式即可

显然,所有方法都保留了polymorphic_value永不为空的不变量.

还有一个与赋值相关的棘手细节:如果两个对象具有相同的动态类型,则只能很好地定义赋值,我们将m_vtable == other.m_vtable检查它们.


为了完整起见,用于启动此课程的缺失部分:

//
//  VTable, with nullable methods for run-time detection of capabilities
//
struct NoneTag {};
struct MoveConstructibleTag {};
struct CopyConstructibleTag {};
struct MoveAssignableTag {};
struct CopyAssignableTag {};

struct polymorphic_value_vtable {
    typedef void (*move_construct_type)(void* src, void* dst);
    typedef void (*copy_construct_type)(void const* src, void* dst);
    typedef void (*move_assign_type)(void* src, void* dst);
    typedef void (*copy_assign_type)(void const* src, void* dst);
    typedef void (*destroy_type)(void* dst);

    move_construct_type move_construct;
    copy_construct_type copy_construct;
    move_assign_type move_assign;
    copy_assign_type copy_assign;
    destroy_type destroy;
};


template <typename Base, typename Derived>
void core_move_construct_function(void* src, void* dst) {
    Derived* derived = reinterpret_cast<Derived*>(src);
    new (reinterpret_cast<Base*>(dst)) Derived(std::move(*derived));
} // core_move_construct_function

template <typename Base, typename Derived>
void core_copy_construct_function(void const* src, void* dst) {
    Derived const* derived = reinterpret_cast<Derived const*>(src);
    new (reinterpret_cast<Base*>(dst)) Derived(*derived);
} // core_copy_construct_function

template <typename Derived>
void core_move_assign_function(void* src, void* dst) {
    Derived* source = reinterpret_cast<Derived*>(src);
    Derived* destination = reinterpret_cast<Derived*>(dst);
    *destination = std::move(*source);
} // core_move_assign_function

template <typename Derived>
void core_copy_assign_function(void const* src, void* dst) {
    Derived const* source = reinterpret_cast<Derived const*>(src);
    Derived* destination = reinterpret_cast<Derived*>(dst);
    *destination = *source;
} // core_copy_assign_function

template <typename Derived>
void core_destroy_function(void* dst) {
    Derived* d = reinterpret_cast<Derived*>(dst);
    d->~Derived();
} // core_destroy_function


template <typename Tag, typename Base, typename Derived>
typename std::enable_if<
    std::is_same<Tag, MoveConstructibleTag>::value,
    polymorphic_value_vtable::move_construct_type
>::type 
build_move_construct_function()
{
    return &core_move_construct_function<Base, Derived>;
} // build_move_construct_function

template <typename Tag, typename Base, typename Derived>
typename std::enable_if<
    std::is_same<Tag, CopyConstructibleTag>::value,
    polymorphic_value_vtable::copy_construct_type
>::type 
build_copy_construct_function()
{
    return &core_copy_construct_function<Base, Derived>;
} // build_copy_construct_function

template <typename Tag, typename Derived>
typename std::enable_if<
    std::is_same<Tag, MoveAssignableTag>::value,
    polymorphic_value_vtable::move_assign_type
>::type 
build_move_assign_function()
{
    return &core_move_assign_function<Derived>;
} // build_move_assign_function

template <typename Tag, typename Derived>
typename std::enable_if<
    std::is_same<Tag, CopyAssignableTag>::value,
    polymorphic_value_vtable::copy_construct_type
>::type 
build_copy_assign_function()
{
    return &core_copy_assign_function<Derived>;
} // build_copy_assign_function


template <typename Base, typename Derived,
          typename MC, typename CC,
          typename MA, typename CA>
polymorphic_value_vtable const& build_vtable() {
    static polymorphic_value_vtable const V = {
        build_move_construct_function<MC, Base, Derived>(),
        build_copy_construct_function<CC, Base, Derived>(),
        build_move_assign_function<MA, Derived>(),
        build_copy_assign_function<CA, Derived>(),
        &core_destroy_function<Derived>
    };
    return V;
} // build_vtable
Run Code Online (Sandbox Code Playgroud)

我在这里使用的一个技巧是让用户配置他将在这个容器中使用的类型是否可以移动构造,移动分配,......通过能力标签.许多操作都是在这些标签上键入的,如果请求的功能将被禁用或效率较低


Aru*_*nmu 6

您可以创建堆栈分配器实例(当然有一些最大限制)并将其作为参数传递给您的pet_maker函数.然后代替定期new执行placement new堆栈分配器提供的地址.

您也可以默认new超过max_size堆栈分配器.