Еще одно решение, а не самое быстрое, но кажется довольно хорошим. По крайней мере, у нее нет ветвей. ;)
uint32 x = ...; // 0x00000001 0x0405a0c0 0x00602000
x |= x << 1; // 0x00000003 0x0c0fe1c0 0x00e06000
x |= x << 2; // 0x0000000f 0x3c3fe7c0 0x03e1e000
x |= x << 4; // 0x000000ff 0xffffffc0 0x3fffe000
x |= x << 8; // 0x0000ffff 0xffffffc0 0xffffe000
x |= x << 16; // 0xffffffff 0xffffffc0 0xffffe000
// now x is filled with '1' from the least significant '1' to bit 31
x = ~x; // 0x00000000 0x0000003f 0x00001fff
// now we have 1's below the original least significant 1
// let's count them
x = x & 0x55555555 + (x >> 1) & 0x55555555;
// 0x00000000 0x0000002a 0x00001aaa
x = x & 0x33333333 + (x >> 2) & 0x33333333;
// 0x00000000 0x00000024 0x00001444
x = x & 0x0f0f0f0f + (x >> 4) & 0x0f0f0f0f;
// 0x00000000 0x00000006 0x00000508
x = x & 0x00ff00ff + (x >> 8) & 0x00ff00ff;
// 0x00000000 0x00000006 0x0000000d
x = x & 0x0000ffff + (x >> 16) & 0x0000ffff;
// 0x00000000 0x00000006 0x0000000d
// least sign.bit pos. was: 0 6 13
Судя по формулировке вашего вопроса (вы использовали слово «скрыть»), вы уже знаете, что здесь происходит. Это явление называется «сокрытие имени». По какой-то причине каждый раз, когда кто-то задает вопрос о , почему происходит скрытие имени , люди, которые отвечают, говорят, что это называется «сокрытие имени» и объясняют, как это работает (что вы, вероятно, уже знаете), или объясните как его переопределить (о котором вы никогда не спрашивали), но, похоже, никто не заботится об актуальном вопросе «почему».
Решение, обоснование скрытия имени, т. е. , почему , он фактически был разработан в C ++, заключается в том, чтобы избежать определенного противоречивого, непредвиденного и потенциально опасного поведения, которое может иметь место, если унаследованный набор перегруженных функций был разрешен для смешивания с текущим набором перегрузок в данном классе. Вероятно, вы знаете, что в C + + перегрузка разрешает работу, выбирая наилучшую функцию из набора кандидатов. Это делается путем сопоставления типов аргументов с типами параметров. Правила сопоставления иногда могут быть сложными и часто приводят к результатам, которые могут быть восприняты нелогично неподготовленным пользователем.
Например, базовый класс B
имеет функцию-член foo
, который принимает параметр типа void *
, и все вызовы foo (NULL)
разрешены на B :: foo (void *)
. Предположим, что скрытие имени отсутствует, и этот B :: foo (void *)
отображается во многих разных классах, спускающихся с B
. Однако, скажем, у некоторого [непрямого, удаленного] потомка D
класса B
определена функция foo (int)
. Теперь, без скрытия имени D
имеет как foo (void *)
, так и foo (int)
видимые и участвующие в разрешении перегрузки. Какую функцию вызовут вызовы foo (NULL)
, если они сделаны через объект типа D
? Они разрешат D :: foo (int)
, поскольку int
является лучшим совпадением для интегрального нуля (т. Е. [D17] NULL ), чем любой тип указателя , Таким образом, по всей иерархии вызывает foo (NULL)
решение одной функции, а в D
(и под) они внезапно разрешаются на другую.
Другой пример приведен в . Дизайн и эволюция C ++ , стр. 77:
class Base {int x; public: virtual void copy (Base * p) {x = p- & gt; Икс; }}; class Derived {int xx; public: virtual void copy (Derived * p) {xx = p- & gt; xx; Основание :: копия (р); }}; void f (База a, Производные b) {a.copy (& amp; b); // ok: копировать базовую часть b b.copy (& amp; a); // error: copy (Base *) скрыта копией (Derived *)}
Без этого правила состояние b будет частично обновлено, что приведет к разрезанию.
Это поведение считалось нежелательным, когда язык был разработан. В качестве лучшего подхода было решено следовать спецификации «сокрытие имени», что означает, что каждый класс начинается с «чистого листа» в отношении каждого имени метода, которое он объявляет. Чтобы переопределить это поведение, пользователю необходимо выполнить явное действие: изначально переобучение унаследованных методов (в настоящее время устарело), теперь явное использование использования-объявления.
Как вы правильно заметили в вашем исходном сообщении (я имею в виду замечание «Не полиморфное»), это поведение можно рассматривать как нарушение отношений IS-A между классами. Это правда, но, видимо, тогда было решено, что в конечном итоге сокрытие имен окажется меньшим злом.
Правила разрешения имен говорят, что поиск имени останавливается в первой области, в которой найдено совпадающее имя. В этот момент правила разрешения перегрузки срабатывают, чтобы найти наилучшее соответствие доступных функций.
В этом случае gogo (int *)
находится (в одиночку) в Derived класс, и поскольку стандартное преобразование из int в int * не выполняется, поиск не выполняется.
Решение состоит в том, чтобы принести объявления Base с помощью объявления using в классе Derived:
с использованием Base :: gogo;
... позволит правилам поиска имен найти всех кандидатов и, следовательно, разрешение перегрузки будет продолжаться, как вы ожидали.
Это «По дизайну». В разрешении перегрузки C ++ для этого типа метода работает следующим образом:
. Поскольку Derived не имеет соответствующей функции с именем« gogo », разрешение перегрузки выходит из строя.
Скрытие имени имеет смысл, потому что оно предотвращает двусмысленности в разрешении имен.
Рассмотрим этот код:
class Base {public: void func (float x) {.. .}} class Derived: public Base {public: void func (double x) {...}} Derived dobj;
Если Base :: func (float)
не был скрыт Derived :: func (double)
в Derived, мы бы назвали функция базового класса при вызове dobj.func (0.f)
, хотя float может быть увеличен до double.
Ссылка: http: // bastian .rieck.ru / блог / сообщений / 2016 / name_hiding_cxx /
nullptr
Я бы возражал против вашего примера, сказав «если вы хотите вызвать версиюvoid *
, вы должны использовать указатель тип & Quot ;. Есть ли другой пример, где это может быть плохо? – GManNickG 17 May 2011 в 20:26d- & gt; foo ()
не даст вам «Is-aBase
», ноstatic_cast & lt; Base * & gt; (d ) - & gt; foo ()
будет i>, включая динамическую отправку. – Kerrek SB 9 January 2014 в 15:36