25 декабря 2008

this == NULL. WTF?

Что вы подумаете, если встретите код типа такого:
if (this == NULL)
...;
Нормально ли что this может быть нулевым, ведь при этом даже к членам класса обратиться нельзя, не то что вызвать виртуальную функцию.
Однако тех, кого не удивляет конструкции вида delete this;, не удивит и проверка this на NULL. В самом деле, this - это обычный указатель, и как любой другой указатель он может быть равен NULL:
class foo {
public:
bool am_i_alive() { return this != NULL; }
};

foo *p = NULL;
assert(!p->am_i_alive());

Но не разумнее ли, спрашивается, проверять указатель на NULL "снаружи" класса (в данном примере - проверять p на NULL), а не внутри его. То есть не вызывать методы объекта, если указатель на него нулевой. И так действительно будет разумнее:
void foo(Bar &bar, IFilter *filter = NULL)
{
if (filter)
filter->init(bar);

for (auto b : bar)
if (!filter || filter->is_accepted(b))
out.insert(b);
}
Единственное преимущество внесения проверки внутрь класса - сокращение вызывающего кода, немного повышающее его написание и читаемость. То есть по сути "синтаксический сахар":
void foo(Bar &bar, IFilter *filter = NULL)
{
filter->init(bar);

for (auto b : bar)
if (filter->is_accepted(b))
out.insert(b);
}
Но ценой этого будет то, что вызываемые функции не должны быть виртуальными, то есть придется каждую виртуальную функцию "обернуть" примерно так:

class IFilter
{
public:
void init(Bar& bar) { if (this) init_impl(bar; }
bool is_accepted(Bar::value_type b) { return !this || is_accepted_impl(b); }
private:
virtual void init_impl(Bar&) = 0;
virtual bool is_accepted_impl(Bar::value_type) = 0;
};

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

class IFilter
{
public:
virtual void init(Bar&) = 0;
virtual bool is_accepted(Bar::value_type) = 0;
};

class NullFilter
{
public:
virtual void init(Bar&) {}
virtual bool is_accepted(Bar::value_type) { return true; }
};

void foo(Bar &bar, IFilter &filter = NullFilter())
{
filter.init(bar);

for (auto b : bar)
if (filter.is_accepted(b))
out.insert(b);
}


В качестве члена какого-нибудь класса это может выглядеть так:
class Foo
{
public:
Foo() : filter(new NullFilter) {}
void set_filter(auto_ptr<IFilter> f) { filter = f; }
void use_filter()
{
// можно смело использовать конструкции filter.get()->xxx()
}
private:
auto_ptr<IFilter> filter;
};
Единственное, что может заставить не послушаться книжку по рефакторингу, так это профайлер ;)

17 декабря 2008

Logical XOR operator in C++

Понадобилось использовать оператор logical XOR и... поначалу не нашел я его в С++. В смысле не нашел оператора, на котором написано "logical XOR". А вообще оператор есть: !=.