четверг, 3 мая 2012 г.

Задача Hello World

Абсолютно just4fun порой люблю задавать C++ программистам (в основном начинающим, хотя я тоже не особо опытный), следующую задачу (я думаю, что это жуткий боян, но многие про неё и не слышали), которую как-то увидел на одном из форумов. А задача предельно проста: Необходимо написать программу, которая печатает фразу "Hello world", но при условии, что функция int main(int argc, char* argv[]) выглядит следующим образом и не может быть изменена (ни сигнатура, ни тело):
int main(int argc, char* argv[])
{
   return 0;
}
Некоторые предлагают использовать например define и переопредить return. Но тогда мы "ломаем" return. Вообщем с макросами можно "играться", но я их предпочитаю использовать в С++ исключительно для защиты от повторного include и возможно для реализации некоторых платформо-зависимых кусков кода. Самый простой для меня например способ - это просто инициализация глоабльной переменной с помощью вызова глобальной функции, в которой как раз и происходит печать:
#include <cstdio>

int foo()
{
  printf("Hello world\n");
  return 10;
}

int n = foo();

int main(int argc, char* argv[])
{
   return 0;
}
Всё просто :) Есть еще вариации с помощью классов (в конструкторе для глобального объекта), потоки ostream и т.д. Вариантов немало.

понедельник, 20 февраля 2012 г.

Создание группы одинаковых элементов и манипуляции с каждым элементов в отдельности

Решил написать для себя небольшую шпаргалку-запоминалку по одному моменту в QML. Всё очень просто и банально, но думаю так в памяти отложиться намного лучше. Задача состоит в следующем: показать как можно создать некоторую коллекцию одинаковых визуальных элементов используя вместе элемент Column и Repeater. Я покажу на очень простом примере, без всякой анимации и красивых фичей, просто функционал, плюс будет некоторый хардкодинг.

Сначало опишем представление нашей кнопки в файле Button.qml:
import QtQuick 1.1
Rectangle {
    id: button
    width: 120
    height: 50
    signal clicked()

    property string text
    property bool active: true

    states: [
        State {
            name: "selected"
            when: button.active
            PropertyChanges {
                target: button
                color: "Red"
            }
        }
    ]

    MouseArea {
        id: mouseArea
        anchors.fill: parent
        onClicked: button.clicked()
        hoverEnabled: true
    }

    Text {
        id: name
        text: button.text
        font.pointSize: 20
        anchors.fill: parent
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
    }
}
Тут достаточно всё просто. Кнопка представляет собой обычный прямоугольник Rectangle размером 120 на 50, в нём находится элемент MouseArea для обработки событий мыши и обычное текстовое поле Text, у которого свойство text, связано с определённым мной свойством text у элемента Rectangle (который является его родителем). Так же определено мной свойство active, которое отображает активна кнопка или нет (под активностью я понимаю нажатие на неё). Соответственно, в состоянии "selected":
State {
   name: "selected"
   when: button.active
   PropertyChanges {
      target: button
      color: "Red"
   }
}
я определяю условие наступления этого состояния - button.active должен быть true (и тогда цвет кнопки станет красным), по умолчанию он false.
Далее следует файл main.qml:
Rectangle {
    width: 120
    height: 150

    property variant buttonData: [
            { text: "Button" },
            { text: "Button" },
            { text: "Button" }
        ]

    Column {
        id: buttonsLayout
        spacing: 5
        property int activeButton
        Repeater {
            model: buttonData
            delegate:
                Button {                
                text: modelData.text + index
                onClicked: {
                    buttonsLayout.activeButton = index
                }
                active: buttonsLayout.activeButton == index
            }
        }
    }
}
Выглядит это примерно следующим образом:

В данном случае используется элемент Repeater, который позволяет создать группу одинаковых элементов (например, несколько кнопок меню или несколько вкладок), унаследованных от Item. Создаваться эта группа элементов будет в Column. Как видно на примере выше мы задали 2 свойства: id, spacing и определили своё activeButton, которое будет содержать номер активной кнопки, т.е. нажатой в данный момент времени. А потом мы добавляем элемент Repeater, которому соотносим модель, где хранится описание 3х кнопок, причём в модели кнопки одинаковые. В delegate определяем тот элемент, который будет создавать и вот стоит обратить внимание на свойство index, доступное только для чтение, это некий счётчик созданных экземпляров. Соответственно в моём примере он используется для двух целей:
1) Добавление номера в надпись:
active: buttonsLayout.activeButton == index
2) Сначала в onClicked мы пишем какое число нужно присвоить внешнему (родительскому) свойству buttonsLayout.activeButton при клике на конкретную кнопку Button. Соответственно, для первой созданной кнопки buttonsLayout.activeButton = 1, для второй buttonsLayout.activeButton = 2, для третьей buttonsLayout.activeButton = 3.
А потом свойство active (это свойство созданного нами элемента Button, типа bool) связываем с условием, при котором index сравниваемым с buttonsLayout.activeButton (для первой созданной кнопки buttonsLayout.activeButton == 1, для второй buttonsLayout.activeButton == 2, для третьей buttonsLayout.activeButton == 3). Когда это выражение будет true, т.е. и свойство active тоже true, соответственно в Button будет сделан переход к состоянию "selected":
State {
    name: "selected"
    when: button.active
    PropertyChanges {
       target: button
       color: "Red"
    }
}
потому что будет выполнено услование when: button.active
Чуть чуть обобщая для лучшего понимания: в onClicked мы говорим какое число присвоить свойству activeButton элемента buttonsLayout, а в active: buttonsLayout.activeButton == index мы говорим какой index будет для каждого элемента и только в уникальном случае для каждого элемента Button, выражение buttonsLayout.activeButton == index будет true и соответственно свойство active тоже будет true.

Всё равно как-то витиевато написал, но главное что сам понял, как можно применять этот достаточно банальный способ, про который не знал ( не знал в первую очерель про свойство index, которые read-only). Если что-то непонятно - пишите в комментариях.

Исходный код примера можно найти на github.

суббота, 12 ноября 2011 г.

Реализация чистых виртуальных функций

Часто, читая форумы и книжки по программированию для начинающих C++ программистов, в разделе, где речь идёт о чистых виртуальных функциях можно встретить подобные фразы:

"У чистой виртуальной (pure virtual function) функции отсутствует реализация"
"тело определено как 0"

Вообщем, начинающий программист читает подобные вещи и запоминает: "Ну всё, значит у чистой виртуальной функции реализации (тела) быть не может". А это в корне неправильно. Реализация может быть. Приведу простой пример:

#include <iostream>

class ISomeInterface
{
public:
 virtual void someMethod() const = 0;
};

void ISomeInterface::someMethod() const
{
 std::cout << "Default implementation of someMethod" << std::endl;
}

class SomeClass : public ISomeInterface
{
public:
 void someMethod() const;
};

void SomeClass::someMethod() const
{
 ISomeInterface::someMethod();
 std::cout << "SomeClass implementation of someMethod" << std::endl;
}

int main( int argc, char* argv[] )
{
 SomeClass obj;
 obj.someMethod();
 return 0;
}
Результатом выполнения данного кода будет:
Default implementation of someMethod
SomeClass implementation of someMethod
Никаких ошибок, предупреждений (я использую gcc 4.6.1 с флагом -Wall) не было. Всё корректно скомпировалось и выполнилось. Во-первых, стоит отметить, что реализация данной функции должна быть определена вне класса, как примере. Вот так не получится:
...
class ISomeInterface
{
public:
 virtual void someMethod() const = 0
 {
  std::cout << "Default implementation of someMethod" << std::endl;
 }
};
...
компилятор выдаст ошибку:
error: pure-specifier on function-definition
И второе, это то, что данная реализация чистой виртуальной функции может быть использована в производных классах только явно:
...
ISomeInterface::someMethod();
...
Данный пост направлен, в первую очередь на то, чтобы начиющие программисты, после таго как они уже станут чуть взрослее (в профессиональном плане) не удивлялись такой ситуации. Или еще хуже, можно ляпнуть такое на собеседовании в серьёзной компании, тем самым вызвать у работодателя мысль, что вы не читали сёрьёзных книг по C++, описывающих эту ситуацию. Тут возникает другой вопрос. А нужно ли это? Когда вообще это применять? И правильно ли это делать? Например, один из гуру C++ Scott Meyers в своей замечательной книге Effective C++, пишет по этому вопросу следущее:
"Кроме песпективы блеснуть перед друзьями-программистами во время вечеринки, знание это особенности вряд ли даст вам что-то значительное". Но дальше всё-таки он приводит примеры применения (правило 36). Я немного скомпоную его мысли, свои взгляды и то, что еще прочитал и пообщался по этой теме.
В реализации данной функции может быть заложено некоторое поведение по-умолчанию или стандартное поведение (как это сделано в обычных виртуальных функциях), но делая её чистой, мы как бы говорим тем, кто будет наследовать этот абстрактный базовый класс, что обязательно нужно переопределить в своём классе и написать реализацию данной функции. А вот использовать эту реализацию по умолчанию или написать свою - это уже на ваше усмотрение.

Может возникнуть такая ситуация, что вообще желательно объявить функцию просто виртуальной (в базовом классе) и указать её реализаю, которая будет использоваться в 95% случаев. Но в 5% случаев, нужно будет определить какую-то особую, специфичную реализацию. Как раз тот факт, что функция чистая виртуальная заставляет нас переопределять её во всех производных классах, и мы точно не забудем это сделать для 5% случаев (это делается, так сказать в целях безопасности). Но при этом мы можем использовать реализацию по умолчанию (из базового класса) для 95% случаев.

Есть и обратная сторона медали, о которой поведал один мой знакомый, авторитетный для меня C++ разработчик - что это выглядит очень неинтуитивно. Когда программист видит чистый виртуальный метод в заголовочном файле, означает, что он пуст и его нельзя вызвать. Я в принципе согласен с этим недостатком. От себя добавлю, что если касаться, например самодокументируемости кода (к чему как правило надо стремиться), то это накладывает определённые противоречения, особенно, если такая штука заложена в какой-то библиотеке.

Как итог, хочу сказать следующее. Да, это по сути некий хак, возможножно упущение или дыра в стандарте, не мне судить, но знать про это надо и знать что такое может быть необходимо. А вот применять или нет - это уже на ваше усмотрение.

суббота, 22 октября 2011 г.

Интересная тонкость поиска имени функции, указанного в выражении вызова функции или просто правило ADL

Совсем недавно купил книгу "C++. Священные знания" Стивена Дьюхерста. Книга отличная, но перевод порой встречается не совсем правильный, про это можно в отзывах почитать. Но всё равно, мне она очень нравится. Вся книга разделена на темы, которые рассматривают как тонкие моменты C++, так и достаточно известные.

Наткнулся я там на очень интересное (интересно для меня в первую очередь, потому что я его не знал, хотя думаю опытные программисты C++ в курсе его) правило. Это тема 25 в этой книге, называется "Поиск, зависимый от типов аргументов". Еще это называется Argument Dependent Lookup, или ADL. Для интереса решил составить небольшую задачку на это правило и спросить, тех C++ разработчиков (в основном с небольшим опытом разработки), кто был онлайн - никто правильно не ответил.

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

#include <iostream>
namespace mynamespace
{
 struct someStruct
 {
  void write() const
  { std::cout << "Some Text" << std::endl; }
 };
 
 void foo( someStruct& obj )
 { obj.write(); }
}

int main( int argc, char* argv[] )
{
 mynamespace::someStruct obj;
 foo( obj );
 return 0;
}
Поидее правильнее было писать:
mynamespace::foo( obj );
т.к. функция foo находится в области видимости mynamespace. Но согласно правилу выше, компилятор при поиске имени функции для её вызова будет проверять и пространства имен, содержащие типы аргументов вызова функции, а у нас аргумент ссылка на объект someStruct& из области видимости mynamespace. Поэтому для foo можно не писать mynamespace::. Какие проблемы это может вызвать эта "фича" языка C++? Вот приведу пример, который задавал вчера всем:
#include <iostream>

namespace mynamespace
{
 class X
 {
  public:
   void getText() const
   { std::cout << "someText" << std::endl; }
 };
 void foo( X* obj )
 { 
  std::cout << "ololo" << std::endl;
  obj->getText(); 
 }
}
 
void foo( mynamespace::X* obj )
{ obj->getText(); }

int main( int argc, char* argv[] )
{
 mynamespace::X obj;
 foo( &obj );
 return 0;
}
Что произойдёт с этим кодом? Ниодин человек не сказал, что он не просто не скомпилится (из-за двусмысленности, я впрочем тоже бы наверное не догадался бы). Компилятор напишет следующее (gcc ):
igor@igor-desktop:~/cpp_test$ g++ -Wall namespace_test.cpp -o namespace_test
namespace_test.cpp: In function ‘int main(int, char**)’:
namespace_test.cpp:24: error: call of overloaded ‘foo(mynamespace::X*)’ is ambiguous
namespace_test.cpp:18: note: candidates are: void foo(mynamespace::X*)
namespace_test.cpp:11: note:                 void mynamespace::foo(mynamespace::X*)

А всё почему? У нас есть глобальная функция foo, но аргумент у неё из mynamespace - mynamespace::X*, соответственно по правилу ADL поиск функции будет происходить и в mynamespace, а там есть своя функция foo. Поэтому будет двусмысленность для компилятора и он просто не даст скомпилировать.

Вот такое интересное правило, а может просто фича языка C++. Я думаю опытные программисты с ним знакомы, а начинающим будет полезно узнать.