Что следует использовать для проверки личности в C ++? - PullRequest
5 голосов
/ 02 октября 2009

У меня есть два указателя на объекты, и я хочу проверить, являются ли они точно таким же объектом самым надежным способом. Я явно не хочу вызывать какие-либо перегрузки operator == и хочу, чтобы они работали независимо от того, какие базовые классы, виртуальные базовые классы и множественное наследование используются.

Мой текущий код такой:

((void*)a) == ((void*)b)

И для моего случая это работает. Тем не менее, это не работает для этого случая:

class B1 {};
class B2 {};
class C : public B1, public B2 {}

C c;
B1 *a = &c;
B2 *b = &c;

Subbing in reinterpert_cast, static_cast или dynamic_cast тоже не работает.


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

Ответы [ 9 ]

8 голосов
/ 02 октября 2009

Если ваши классы действительно соответствуют заданным, то это невозможно, так как во время выполнения не хватает информации для восстановления необходимой информации.

Если это на самом деле полиморфные классы с виртуальными функциями, похоже, что dynamic_cast<void *> - это ответ. Возвращает указатель на наиболее производный объект. Ваш чек будет тогда dynamic_cast<void *>(a)==dynamic_cast<void *>(b).

См. Пункт 7 здесь:

http://www.csci.csusb.edu/dick/c++std/cd2/expr.html#expr.dynamic.cast

Я подозреваю, что обычные dynamic_cast проблемы применимы - то есть, нет гарантии, что это будет быстро, и ваши классы должны быть полиморфными.

Боюсь, что это не та функция, которую я использовал сам, но я видел, что люди, у которых есть эта идея, достаточно часто предлагали ее, она широко поддерживается и работает как рекламируется.

3 голосов
/ 02 октября 2009

Есть простой и трудный путь.

Самый простой способ - ввести пустой виртуальный базовый класс. Каждый объект, унаследованный от такого класса, получает указатель на общую точку в «реальном» объекте, что вам и нужно. Указатель немного перегружен, но нет веток или чего-либо еще.

class V {};
class B1 : public virtual V {}; // sizeof(B1) = sizeof(void*)
class B2 : public virtual V {}; // sizeof(B2) = sizeof(void*)
class D : public B1, public B2 {}; // sizeof(D) = 2*sizeof(void*)

bool same( V const *l, V const *r ) { return l == r; }

Сложный путь - попытаться использовать шаблоны. Здесь уже есть несколько хаков ... при взломе с помощью шаблонов помните, что вы, по сути, заново изобретаете часть языка, просто с надеждой снизить накладные расходы путем управления информацией времени компиляции. Можем ли мы снизить накладные расходы виртуального базового класса и устранить этот указатель? Это зависит от того, сколько общности вам нужно. Если ваши базовые классы могут быть организованы несколькими различными способами в производном объекте, то, безусловно, есть информация, которую вы не можете получить во время компиляции.

Но если ваша иерархия наследования является перевернутым деревом (то есть вы строите большие объекты с помощью множества множественного наследования) или несколькими такими деревьями, вы можете просто продолжить и привести указатели к наиболее производному типу, как этот :

class C; // forward declare most derived type
class T { public: typedef C base_t; }; // base class "knows" most derived type
class B1: public T { int a; };
class B2: public T { int b; };
class D: public B1, public B2 { int c; };

 // smart comparison function retrieves most-derived type
 // and performs simple addition to make base pointers comparable
 // (if that is not possible, it fails to compile)
template< class ta, class tb >
bool same( ta const *l, tb const *r ) {
        return static_cast< typename ta::base_t const * >( l )
         == static_cast< typename tb::base_t const * >( r );
}

Конечно, вы не хотите передавать указатели NULL на эту «оптимизированную» версию.

3 голосов
/ 02 октября 2009

Нет общего способа сделать это. Субобъекты базового класса, как правило, не знают, что это так, поэтому, если у вас есть только указатель на подобъект базового класса, у вас нет никаких средств для получения указателя на самый производный объект, к которому он принадлежит, если вы заранее не знаю тип последнего.

Давайте начнем с этого:

 struct B1 { char b1; };
 struct B2 { char b2; };
 struct D : B1, B2 { char d; };

 // and some other type...
 struct Z : B2, B1 { };

Рассмотрим типичную реализацию макета в памяти D. В отсутствие vtable единственное, что у нас есть, это необработанные данные (и, возможно, заполнение):

       Offset    Field
       ------    -----
    /       0    b1     >- B1
 D-<        1    b2     >- B2
    \       2    d  

У вас есть два указателя:

B1* p1;
B2* p2;

Каждый указатель фактически указывает на один char в экземпляре D. Однако, если вы не знаете этого заранее, как вы можете сказать? Также существует вероятность того, что указатели могут скорее указывать на подобъекты в экземпляре Z, и, глядя на сами значения указателя, явно невозможно сказать; и при этом вы (или компилятор) ничего не можете сделать из данных, на которые ссылается указатель, поскольку это всего лишь один байт данных в структуре.

1 голос
/ 02 октября 2009

За исключением умных указателей (которые на самом деле не указатели, а объекты классов), перегрузка operator== для указателей невозможна, поэтому приведение не должно быть необходимым.

Конечно, сравнение указателей разных типов может не сработать. Почему вы думаете, что вам нужно это сделать?

0 голосов
/ 02 октября 2009

Если вам нужно сравнить идентичность ваших объектов, почему бы вам не дать им один? В конце концов, именно вы решают, что делает личность объекта. Пусть компилятор сделает это, и вы ограничены ограничениями компилятора.

Что-то на пути ...

class identifiable {
    public:
    long long const /*or whatever type*/ identity;
    static long long sf_lFreeId() { 
       static long long lFreeId = 0;
       return lFreeId++; // not typesafe, yet
    }
    identifiable(): identity( sf_lFreeId() ) {}
    bool identical( const identifiable& other ) const { 
      return identity == other. identity;
    }
};

class A : public identifiable {
};

....

A a1, a2;
A& a3 = a1;

assert( !a1.identical(a2) );
assert( a1.identical( a3 ) );
0 голосов
/ 02 октября 2009

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

#include "boost/utility.hpp"

class some_class {};

int main() {
  some_class s;
  some_class* p=boost::addressof(s);
}
0 голосов
/ 02 октября 2009

Итак, вы ищете решение времени компиляции. Я не верю, что это возможно, как указано в C ++. Вот мысленный эксперимент:

Файл Bases.hpp:

class B1 {int val1;};
class B2 {int val2;};

Файл Derived.hpp:

#include <Bases.hpp>
class D : public B1, public B2 {};

Файл Composite.hpp:

#include <Bases.hpp>
class C
{
   B1 b1;
   B2 b2;
};

Файл RandomReturn.cpp:

#include <Composite.hpp>
#include <Derived.hpp>
#include <cstdlib>

static D derived;
static C composite;

void random_return(B1*& left, B2*& right)
{
    if (std::rand() % 2 == 0)
    {
        left=static_cast<B1*>(&derived);
        right=static_cast<B2*>(&derived);
    }
    else
    {
        left=&composite.b1;
        right=&composite.b2;
    }
}

Теперь предположим, что у вас есть:

#include <Bases.hpp>
#include <iostream>

extern void random_return(B1*& , B2*& );

// some conception of "is_same_object"    
template <...> bool is_same_object(...) ...

int main()
{
    B1 *left;
    B2 *right;

    random_return(left,right);
    std::cout<<is_the_same_object(left,right)<<std::endl;
}

Как мы могли бы реализовать is_same_object здесь, во время компиляции, не зная ничего о class C и class D?

С другой стороны, если вы хотите изменить гипотезы, это должно быть работоспособно:

class base_of_everything {};
class B1 : public virtual base_of_everything {};
class B2 : public virtual base_of_everything {};

class D : public B1, public B2, public virtual base_of_everything {};

...
// check for same object
D d;
B1 *b1=static_cast<B1*>(&d);
B2 *b2=static_cast<B2*>(&d);

if (static_cast<base_of_everything*>(b1)==static_cast<base_of_everything*>(b2))
{
    ...
}
0 голосов
/ 02 октября 2009

Ваш подход сработал для меня даже в указанном вами случае:

class B1 {};
class B2 {};
class C : public B1, public B2 {};

int main() {
  C c;
  B1 *a = &c;
  B2 *b = &c;

 if ((void*)a == (void*)b) {
  printf("equal");
 }
 else {
  printf("not equal!");
 }

}

печатает "равный" здесь ..

0 голосов
/ 02 октября 2009

Вы можете проверить, указывают ли объекты на совпадение в памяти (воровство из ответа Марка Рэнсома). Это вызывает неопределенное поведение, но должно делать то, что вы хотите на любом разумном компиляторе:

template <typename T1, typename T2>
bool is(const T1 *left, const T2 * right)
{
    const char *left_begin=reinterpret_cast<const char*>(left);
    const char *left_end=left_begin+sizeof(*left);

    const char *right_begin=reinterpret_cast<const char*>(right);
    const char *right_end=right_begin+sizeof(*right);

    return ( left_begin  <= right_begin && right_begin < left_end) ||
           ( right_begin <= left_begin  && left_begin < right_end);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...