Как проверить, указывают ли два указателя на один и тот же объект или нет? - PullRequest
14 голосов
/ 02 апреля 2012

Рассмотрим два указателя

A* a; 
B* b;

И A, и B являются полиморфными классами. Как проверить, указывают ли a и b на один и тот же объект или нет?

Точнее, давайте укажем, что a и b указывают на один и тот же объект, если существует некоторый объект d типа D, такой что * * и * b находятся где-то в иерархии классов d.

Я бы предложил следующее решение:

dynamic_cast<void*>(a) == dynamic_cast<void*>(b)

Действительно, согласно стандарту,

dynamic_cast<void*>(v) 

возвращает »указатель на наиболее производный объект, на который указывает v. (N3242.pdf: § 5.2.7 - 7). Если наиболее производным для обоих является один и тот же объект, то указатели указывают на один и тот же объект.

Я почти уверен, что с практической точки зрения он всегда должен работать правильно. Но теоретически, на первый взгляд, предложенное равенство, по-видимому, приводит к ложному положительному результату, например, в случае, если b указывает на первый член A (а не на предка A). Хотя практически невозможно получить равные адреса для A и его члена, так как указатель виртуальной таблицы A должен быть расположен перед этим членом, стандарт не предписывает виртуальные таблицы и ничего не говорит о макете класса.

Итак, мои вопросы:

  1. Является ли предлагаемое решение правильным со стандартной точки зрения?

  2. Есть ли какие-либо предостережения о частном (защищенном) наследовании или cv-квалификации?

  3. Есть ли лучшие решения?

[РЕДАКТИРОВАТЬ]

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

 // proposed impplementation:
template<typename P, typename Q> 
bool test_ptrs(const P* p, const Q* q)
{
  return (dynamic_cast<const void*>(p) ==  dynamic_cast<const void*>(q));
}


struct Root
{
  virtual ~Root(){};
};

struct A: public Root // nonvirtually
{
};

struct B: public Root // nonvirtually
{
};

struct C: public A, B  // nonvirtual diamond started with Root
{
  Root another_root_instance;
};

int main()
{
  C c;

  A* pa= &c;
  B* pb= &c;

  bool b = (dynamic_cast<void*>(pa) ==  dynamic_cast<void*>(pb));

  Root* pra= dynamic_cast<Root*> (pa); 
  Root* prb= dynamic_cast<Root*> (pb);

  //Root* prc= dynamic_cast<Root*> (&c); // runtime error, ambiguous cast
  Root* prr= dynamic_cast<Root*>(pra);

  Root* pcar= dynamic_cast<Root*>(pra);
  Root* pcbr= dynamic_cast<Root*>(prb);

  if(
      test_ptrs(pa, pb) 
      && test_ptrs(pra, prb)
      && !test_ptrs(pa,&c.another_root_instance)
    )
  {
    printf("\n test passed \n");
  }
}

Ответы [ 3 ]

2 голосов
/ 02 апреля 2012

Мне кажется, что наименее вонючий способ справиться с этим - ввести базовый класс для A & B:

#include <iostream>

struct Base
{
    virtual ~Base() {};
};

struct A : public virtual Base
{
    int a;
    virtual ~A() {};
    virtual void afunc() {};
};



struct B : public virtual Base
{
    int b;
    virtual ~B() {};
    virtual void bfunc() {};
};

struct C: A, B
{};

int main()
{
    C c;
    A *a = &c;
    B *b = &c;

    std::cout << "a* == " << &(*a) << std::endl;
    std::cout << "b* == " << &(*b) << std::endl;
    std::cout << "a == b == " << ((void*)a == (void*)b) << std::endl;

    Base* ba = a;
    Base* bb = b;

    std::cout << "ba* == " << &(*ba) << std::endl;
    std::cout << "bb* == " << &(*bb) << std::endl;
    std::cout << "ba == bb == " << (ba == bb) << std::endl;

    return 0;
}
1 голос
/ 02 апреля 2012

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

  • Адрес, на который указывает изменение в зависимости от типа указателя.

Следовательно, теоретически мы можем сказать, как

a * и b * указывают на один и тот же объект, если существует некоторый объект c типа C, такой что * * и * b находятся где-то в иерархии классов C. "

Логически

мы должны вернуться к приведенному выше утверждению, как «a * и b * указывает на один и тот же объект , но имеет собственную зону доступа в памяти obj c типа C, так что * a и * b находятся где-то в иерархии классов C. ""

struct Aa { int a; Aa() {a= 0;} };

struct Bb 
{   int b;
    Bb() { b= 0;}
}; 
struct C: Aa, Bb {      
}; 

C c; 
Aa *a1 = &c; 
Aa *a2 = &c; 
Bb *b1 = &c; 
Bb *b2 = &c; 

cout  << &c << "\t"<< &(*a1)<<"\t"<< &(*a2)<<endl;
cout  << &c << "\t"<< &(*b1)<<"\t"<< &(*b2)<<endl;

Вывод:

  • & c 0x0012fd04
  • & (* a1) 0x0012fd04
  • & (* a2) 0x0012fd04
  • & (* b1) 0x0012fd08
  • & (* b2) 0x0012fd08

Хотя это не решит вашу проблему, у нас есть смысл сделать вывод.

0 голосов
/ 02 апреля 2012

Поскольку с dynamic_cast вы также можете разыграть "в сторону" в иерархии типов, я бы предложил:

(b != nullptr? dynamic_cast<B*>(a) == b : a == nullptr)

Если a указывает на некоторый подобъект в начале *b, тоdynamic_cast<B*>(a) обязательно вернет нулевой указатель (потому что B не может содержать себя).Поэтому, если b не является нулевым указателем, dynamic_cast<B*>(a) == b будет успешным, только если оба совместно используют один и тот же самый производный класс.Случай, когда b является нулевым указателем, должен рассматриваться конкретно, потому что, если a не является нулевым, но не указывает на класс, производный от B, тест dynamic_cast завершится неудачей.

ОднакоВ некоторых ситуациях, связанных с множественным наследованием, это решение дает ложный отрицательный результат (в отличие от вашего решения, которое никогда не дает ложных отрицательных результатов, но может давать ложные положительные результаты).Однако иерархии классов, в которых это может произойти, - это иерархии, которые я бы сказал, что вам не следует создавать (а именно, один и тот же производный класс, содержащий несколько косвенных баз типа B).Вы можете уменьшить количество ложных негативов, протестировав снова, поменяв роль a и b (тогда только в том случае, если и A и B неоднозначны в наиболее производном классе,тест не пройден).

Вы также можете объединить свой и мой тест, чтобы получить три результата:

  • Оба теста пройдены успешно: указатели определенно относятся к одному и тому же объекту (или не имеют нулевого значения).
  • Оба теста не пройдены: указатели определенно не относятся к одному и тому же объекту.
  • Только мой тест не пройден: Либо ваш тест дал ложное срабатывание, или мой тест дал ложный отрицательный результат.Вы не можете точно сказать, являются ли оба объекта одним и тем же, но, по крайней мере, вы можете сказать, что не можете сказать.
...