Подтвердить что ты не робот

Как проверить, указывают ли два указателя на один и тот же объект или нет?

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

A* a; 
B* b;

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

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

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

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

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

dynamic_cast<void*>(v) 

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

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

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

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

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

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

[EDIT]

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

 // 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");
  }
}
4b9b3361

Ответ 1

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

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

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

a * и b * указывает на один и тот же объект, если существует некоторый объект c типа C, так что оба * a и * 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

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

Ответ 2

Мне кажется, что наименее вонючий способ справиться с этим - ввести базовый класс для 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;
}

Ответ 3

Так как с 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 неоднозначны в самом производном классе, тест завершится с ошибкой).

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

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