Проверка того, является ли переданный в функцию void* либо shared_ptr, либо unique_ptr

Я работаю над созданием функции для класса, и параметр объявлен как void*, однако внутри функции мне нужно проверить, является ли этот void* либо shared_ptr, либо unique_ptr. Есть ли способ проверить этот тип ситуации?

Это то, с чем я работаю до сих пор; мой класс является типом шаблона и не хранит никаких переменных-членов. У него есть конструктор по умолчанию, и его также можно построить, передав shared_ptr<Type> или unique_ptr<Type>, и он имеет несколько функций allocate(), где они выполняют один и тот же тип работы.

#ifndef ALLOCATOR_H
#define ALLOCATOR_H

#include <memory>
#include <iostream>

template<class Type>
class Allocator {
public:
    Allocator(){}
    Allocator( Type type, void* pPtr );
    Allocator( std::shared_ptr<Type>& pType );
    Allocator( std::unique_ptr<Type>& pType );
    // ~Allocator(); // Default Okay

    void allocate( std::shared_ptr<Type>& pType );
    void allocate( std::unique_ptr<Type>& pType );
    void allocate( Type type, void* pPtr );

private:
    Allocator( const Allocator& c ); // Not Implemented
    Allocator& operator=( const Allocator& c ); // Not Implemented

}; // Allocator

#include "Allocator.inl"

#endif // ALLOCATOR_H

В моем файле *.cpp есть только #include "Allocator.h", так как все реализации находятся в моем файле *.inl.

Мои два конструктора: Allocator( std::shared_ptr<Type>& pType ); и Allocator( std::unique_ptr<Type>& pType ); вместе с обеими соответствующими функциями allocate() работают нормально. У меня проблемы с конструктором Allocator( Type type, void* pPtr ); и его функцией сопоставления.

Сам конструктор прост, поскольку все, что он делает, это вызывает функцию сопоставления, передавая ей переменные.

template<class Type>
Allocator<Type>::Allocator( Type type, void* pPtr ) {
    allocate( type, eType, pPtr );
}

Именно в реализации функции я борюсь.

template<class Type>
void Allocator<Type>::allocate( Type type, void* pData ) {
    if ( pData == reinterpret_cast<void*>( std::shared_ptr<Type ) ) {
        std::shared_ptr<Type> pShared;
        pShared.reset( new Type( type ) );
        pData = reinterpret_cast<void*>( pShared );

    } else if ( pData == reinterpret_cast<void*>( std::unique_ptr<Type ) ) {
        std::unique_ptr<Type> pUnique;
        pUnique.reset( new Type( type ) );
        pData = reinterpret_cast<void*>( pUnique );

    } else {
        std::cout << "Error invalid pointer type passed in << std::endl
                  << "must be either a std::shared_ptr<Type> << std::endl
                  << "or a std::unique_ptr<Type> << std::endl;
    }            
}

Помимо проверки того, является ли переданный void* std::shared_ptr<Type> или std::unique_ptr<Type>, другие вопросы, которые у меня могут возникнуть, заключаются в том, является ли мое использование reinterpret_cast<void*> правильным способом преобразования интеллектуального указателя в пустой указатель, и если нет, то как этого можно достичь ?


person Francis Cugler    schedule 07.10.2015    source источник
comment
Странные вещи. Какая в этом необходимость?   -  person πάντα ῥεῖ    schedule 08.10.2015
comment
В этом нет никакой основной необходимости, это было просто самостоятельным упражнением, чтобы укрепить мои навыки работы с шаблонами. Я подумал, что это будет хороший класс служебного типа, в котором вы можете создать его экземпляр с желаемым типом, а затем вызвать его функцию-член для создания либо shared_ptr, либо unique_ptr этого типа, либо сделать то же самое с помощью его конструкторов.   -  person Francis Cugler    schedule 08.10.2015
comment
Вы просто не можете этого сделать, указатель void не несет с собой никакой информации о типе, если только вы не можете подразумевать это с помощью разумного reinterpret_cast, где вы точно знаете, как интерпретировать расположение памяти, на которое указывает указатель void*.   -  person πάντα ῥεῖ    schedule 08.10.2015
comment
Ваш void* указывает на общий/уникальный PTR? Или это указывает на то, на что указывает общий/уникальный ptr? Или вы действительно пытаетесь интерпретировать байты общего/уникального указателя как void*? Если третья возможность (как следует из вашего другого кода), то я думаю, что вы обречены. Среди других проблем нет гарантии, что размер указателя void будет таким же, как размер общего/уникального указателя (и я полагаю, что shared_ptr, вероятно, никогда не будет того же размера).   -  person TheUndeadFish    schedule 08.10.2015
comment
Метод не знает, собирается ли пользователь передать shared_ptr или unique_ptr при вызове или вызове функции. Я использовал void* в его объявлении, но когда функция используется, нужно передать одно или другое.   -  person Francis Cugler    schedule 08.10.2015
comment
В какой-то момент я подумал об удалении типа шаблона из самого класса и просто о том, чтобы сами функции были шаблонами функций. Для этого потребуются две отдельные функции.   -  person Francis Cugler    schedule 08.10.2015
comment
Это было самостоятельным упражнением, чтобы укрепить мои навыки работы с шаблонами, и я подумал, что это может быть удобный служебный класс для быстрого создания общих и уникальных указателей определенного типа на лету.   -  person Francis Cugler    schedule 08.10.2015
comment
Класс, который вы показали, уже имеет перегрузки для shared_ptr и unique_ptr... поэтому я не понимаю, почему перегрузка void* вообще должна беспокоиться о них. Вы пытаетесь сжать реализации allocate в одну функцию (согласно ответу Реми)? Если это так, то кажется, что это слишком усложняет то, что должно быть парой простых отдельных перегрузок. В противном случае вы вызываете класс способом, который уже не обрабатываются текущими перегрузками std::shared_ptr<Type>& и std::unique_ptr<Type>&? Если да, то покажите снаружи, чего вы действительно пытаетесь достичь.   -  person TheUndeadFish    schedule 08.10.2015
comment
Благодаря отзывам всех, я смог обновить этот класс, я предоставил его в качестве ответа.   -  person Francis Cugler    schedule 08.10.2015


Ответы (3)


Указатель void* не несет никакой информации о типе. Что вам нужно сделать, так это передать дополнительное значение вместе с void*, чтобы указать, на что указывает void*, а затем вы можете соответствующим образом привести его тип.

#ifndef ALLOCATOR_H
#define ALLOCATOR_H

#include <memory>
#include <iostream>

template<class Type>
class Allocator {
public:
    enum AllocateType { eSharedPtr, eUniquePtr };

    Allocator() {}
    Allocator( Type type, std::shared_ptr<Type>& pData );
    Allocator( Type type, std::unique_ptr<Type>& pData );
    // ~Allocator(); // Default Okay

    void allocate( Type type, std::shared_ptr<Type>& pData );
    void allocate( Type type, std::unique_ptr<Type>& pData );

private:
    Allocator( const Allocator& c ); // Not Implemented
    Allocator& operator=( const Allocator& c ); // Not Implemented

    void allocate( Type type, AllocateType eDataType, void* pData );

}; // Allocator

#include "Allocator.inl"

#endif // ALLOCATOR_H

template<class Type>
Allocator<Type>::Allocator( Type type, std::shared_ptr<Type>& pData ) {
    allocate( type, pData );
}

template<class Type>
Allocator<Type>::Allocator( Type type, std::unique_ptr<Type>& pData ) {
    allocate( type, pData );
}

template<class Type>
void Allocator<Type>::allocate( Type type, std::shared_ptr<Type>& pData ) {
    allocate( type, eSharedPtr, &pData );
}

template<class Type>
void Allocator<Type>::allocate( Type type, std::unique_ptr<Type>& pData ) {
    allocate( type, eUniquePtr, &pData );
}

template<class Type>
void Allocator<Type>::allocate( Type type, AllocateType eDataType, void* pData ) {
    switch (eDataType) {
        case eSharedPtr: {
            static_cast<std::shared<Type>*>(pData)->reset( new Type( type ) );
            break;
        }
        case eUniquePtr: {
            static_cast<std::unique_ptr<Type>*>(pData)->reset( new Type( type ) );
            break;
        }
    }
}

In which case, I wouldn't even bother trying to funnel everything through a single function to begin with:

#ifndef ALLOCATOR_H
#define ALLOCATOR_H

#include <memory>
#include <iostream>

template<class Type>
class Allocator {
public:
    Allocator() {}
    Allocator( Type type, std::shared_ptr<Type>& pData );
    Allocator( Type type, std::unique_ptr<Type>& pData );
    // ~Allocator(); // Default Okay

    void allocate( Type type, std::shared_ptr<Type>& pData );
    void allocate( Type type, std::unique_ptr<Type>& pData );

private:
    Allocator( const Allocator& c ); // Not Implemented
    Allocator& operator=( const Allocator& c ); // Not Implemented

}; // Allocator

#include "Allocator.inl"

#endif // ALLOCATOR_H

template<class Type>
Allocator<Type>::Allocator( Type type, std::shared_ptr<Type>& pData ) {
    allocate( type, pData );
}

template<class Type>
Allocator<Type>::Allocator( Type type, std::unique_ptr<Type>& pData ) {
    allocate( type, pData );
}

template<class Type>
void Allocator<Type>::allocate( Type type, std::shared_ptr<Type>& pData ) {
    pData.reset( new Type( type ) ) ;
}

template<class Type>
void Allocator<Type>::allocate( Type type, std::unique_ptr<Type>& pData ) {
    pData.reset( new Type( type ) );
}
person Remy Lebeau    schedule 07.10.2015
comment
Это то, к чему я стремился, и спасибо за разъяснение. Однако я заметил, что вы используете static_cast, а не reinterpret_cast. Однако, поскольку этот класс не хранит никаких внутренних значений и имеет только функции-члены. Я как-то пошел другим путем. - person Francis Cugler; 08.10.2015
comment
reinterpret_cast предназначен для использования при приведении типов между целыми числами и указателями или между несвязанными типами указателей. Однако на самом деле это не включает void*. Вы можете static_cast с void* на любой другой тип указателя (любой тип указателя может быть напрямую назначен void* без использования приведения вообще). - person Remy Lebeau; 08.10.2015
comment
Лабо Спасибо за эту информацию; Я довольно часто использовал static_cast и dynamic_cast, но не так часто использовал reinterpret_cast. Спасибо за легкое разъяснение основных отличий. - person Francis Cugler; 08.10.2015
comment
Благодаря отзывам всех, я смог обновить этот класс, я предоставил его в качестве ответа. - person Francis Cugler; 08.10.2015

Вы не можете проверить, какой тип void*. Это void*. Вот и все. Это не похоже на boost::any, где он хитро скрывает какую-то другую информацию о типе. Это просто void*. Вы не можете проверить, из какого он типа. Вы не можете проверить, произошло ли оно от определенного типа. У вас ноль информации. Почтовый индекс Нада. пшик. Пустота.

person Barry    schedule 07.10.2015
comment
Ну, могут быть применимы странные хаки, специфичные для машины/компилятора (канарейки и тому подобное). Хотя нет стандартного способа сделать это, полностью согласен. - person πάντα ῥεῖ; 08.10.2015
comment
Я думал об этом, поэтому, если невозможно проверить, является ли указатель, переданный в функцию, тем или иным типом, как я узнаю, какой метод вызывать? Если кто-то передает shared_ptr по сравнению с unique_ptr, вот где я застрял. Я думал об использовании перечисляемого типа для указания, но если кто-то использует перечисление SHARED_PTR, но передается unique_ptr, это будет проблемой, и наоборот. - person Francis Cugler; 08.10.2015
comment
@FrancisCugler На самом деле вам следует передать умный указатель, а не void*. Не бросайте информацию о типе на пол, если она вам действительно нужна! - person Barry; 08.10.2015
comment
Благодаря отзывам всех, я смог обновить этот класс, я предоставил его в качестве ответа. - person Francis Cugler; 08.10.2015

После некоторого размышления и осознания того, что этот класс не хранит внутри никаких переменных-членов и содержит только функции, выполняющие конкретную задачу, я выбрал другой подход. Я благодарю всех за их отзывы и ответы, так как они дают хорошие и достоверные ответы на основные вопросы, которые я изначально задавал. Вот чего я смог добиться благодаря вашим советам. Я убрал оговорку, что сам этот класс является шаблоном, я сделал конструктор по умолчанию приватным. Я убедился, что каждая функция является шаблоном функции, и сделал их статическими. Вот мой новый класс:

Распределитель.h

#ifndef ALLOCATOR_H
#define ALLOCATOR_H

#include <memory>

class Allocator {
public:
    template<class Type>
    inline static void allocate( std::shared_ptr<Type>& pShared, const Type& type = Type() );

    template<class Type>
    inline static void allocate( std::unique_ptr<Type>& pUnique, const Type& type = Type() );

private:
    Allocator();
    Allocator( const Allocator& c );
    Allocator& operator=( const Allocator& c );

}; // Allocator

#include "Allocator.inl"

#endif // Allocator

Распределитель.cpp

#include "Allocator.h"

Распределитель.inl

template<class Type>
inline void Allocator::allocate( std::shared_ptr<Type>& pShared, const Type& type ) {
    pShared.reset( new Type( type ) );  
}

template<class Type>
inline void Allocator::allocate( std::unique_ptr<Type>& pUnique, const Type& type ) {
    pUnique.reset( new Type( type ) );
}

Это делает использование этого класса простым.

main.cpp

#include <iostream>
#include <conio.h>

#include "Allocator.h"

class A {
private:
    int m_a;
public:
    explicit A( int a = 0 ) : m_a( a ) {}
    A( const A& a ) { this->m_a = a.m_a; }

    int  getA() const { return m_a; }
    void setA( int a ) { m_a = a; }
}; // A

int main() {

    // Creating Smart A Pointer Just From A Class Type
    std::shared_ptr<A> pShared;
    std::unique_ptr<A> pUnique;

    Allocator::allocate( pShared );
    Allocator::allocate( pUnique );

    std::cout << "Shared: " << pSharedA->getA() << std::endl;
    std::cout << "Unique: " << pUniqueA->getA() << std::endl;

    pSharedA->setA( 4 );
    pUniqueA->setA( 5 );

    std::cout << "Shared: " << pSharedA->getA() << std::endl;
    std::cout << "Unique: " << pUniqueA->getA() << std::endl;

    // Create A Smart Pointer From An Object Of Type In This Case A
    // This next sections does rely on the fact that Type in this case A, should have a copy constructor defined.

    A a1( 3 );
    std::shared_ptr<A> sharedA;
    std::unique_ptr<A> uniqueA;
    Allocator::allocate( sharedA, a1 );
    Allocator::allocate( uniqueA, a1 );

    std::cout << "Shared: " << sharedA->getA() << std::endl;
    std::cout << "Unique: " << uniqueA->getA() << std::endl;

    sharedA->setA( 6 );
    uniqueA->setA( 7 );

    std::cout << "Shared: " << sharedA->getA() << std::endl;
    std::cout << "Unique: " << uniqueA->getA() << std::endl;

    pSharedA.reset();
    pUniqueA.release();
    pUniqueA.reset();

    sharedA.reset();
    uniqueA.release();
    uniqueA.reset();

    std::cout << std::endl << "Press Any Key To Quit" << std::endl;
    _getch();

    return 0;

} // main

Это также устраняет необходимость приведения типов, сравнений и т. д., повышая эффективность выполняемой работы. Я мог бы даже сделать еще один шаг вперед, объявив все эти функции встроенными.

ИЗМЕНИТЬ

Я последовал совету Реми Лебо и удалил две перегруженные функции, добавив Type type в качестве параметра по умолчанию, а также изменил его для передачи по константной ссылке. Я также сделал функции встроенными.

person Francis Cugler    schedule 07.10.2015
comment
Вам действительно не нужны дополнительные 2 allocate() перегрузки, которые имеют type параметров. Всего у вас может быть 2 перегрузки, которые имеют type параметров со значениями по умолчанию, если они не указаны вызывающей стороной, например: static void allocate( std::shared_ptr<Type>& pShared, Type type = Type() ); ... Allocator::allocate( pShared ); ... Allocator::allocate( shared, a1 ); Вы также можете рассмотреть возможность изменения Type type на const Type &type, чтобы избежать ненужных копий. - person Remy Lebeau; 08.10.2015
comment
@Remy Lebeau Совершенно верно; Я не думал об этом. Думаю, я отредактирую этот ответ, чтобы отразить ваш ответ. - person Francis Cugler; 08.10.2015