понедельник, 12 декабря 2011 г.

WinDBG kernel debugging with VirtualBox

Настройка WinDBG kernel debugger для отладки гостевой системы в VirtualBox.
Коротко, ничего лишнего.

WinDBG устанавливается на хосте, отлаживается система внутри VirtualBox.
Считаем что ОС Windows Vista или Windows 7 (x86 или x64) уже установлены.

1. Останавливаем эмуляцию, открываем свойства виртуальной машины.
Переходим на вкладку "COM-порты", включаем порт COM1.
Режим порта "Хост-канал", чекбокс "Создать канал" установлен.
Прерывание 4, порт 0x3F8.
Путь к порту/файлу: \\.\pipe\wmdbgcom1

2. Включаем эмуляцию, ждем загрузки гостевой ОС. Открываем cmd.exe c правами админа.
Вводим:
bcdedit /dbgsettings SERIAL DEBUGPORT:1 BAUDRATE:115200
bcdedit /bootdebug on
bcdedit /debug on


3. Запускаем WinDBG на хосте, открываем File - Kernel Debug, вкладка "COM"
В окошке вводим наши параметры:
Port: \\.\pipe\wmdbgcom1
Baud Rate: 115200
Установить галочку "Pipe"

Нажимаем ОК

4. Перезагружаем гостевую ОС в VirtualBox

5. Загружаем отладочные символы из интернета: ждем когда отладчик прицепится, ждем загрузки гостевой ОС, переходим в WinDBG, жмем Ctrl+Break. После остановки и перехода в режим отладки, в главном меню WinDBG выбираем пункт File - Symbol search path
Вводим: SRV*f:\localsymbols*http://msdl.microsoft.com/download/symbols
Жмем ОК.
В командной строке отладчика вводим: .reload
Ждем завершения операции

6. Все, отлаживаемся на здоровье

Важно: без запущенного отладчика гостевая ОС будет стартовать оооочень долго. Поэтому без WinDBG теперь эта виртуалка работать не будет. Чтобы вернуть все назад, нужно отключить отладку в гостевой ОС. Или как вариант - выключить COM порт в настройках VirtualBox (это проще)

Читать дальше...

понедельник, 26 сентября 2011 г.

Проблема extension-методов с типом dynamic

"Динамический тип" появился в .NET 4.0. Суть его состоит в привязывании методов не на уровне компиляции, а на уровне исполнения. Почитать про него можно к примеру тут http://habrahabr.ru/blogs/net/43773/

Но ужасное открытие меня ожидало при разработке библиотеки с использованием типов dynamic. "Расширительные" методы на нем не только не работают, но даже не выдают ошибки. Точнее, генерируют исключение уже во время работы.

Хотя такое поведение логично (тип-то динамический), в коде оно создает дикую путаницу, особенно при рефакторинге. Выхода три:
1. Не использовать extension методы
2. Не использовать dynamic типы
3. Терпеть как есть, сохранять бдительность и качественно тестировать код. Как вариант - можно вызывать методы расширения как обычные статические методы для dynamic:

dynamic dynamicObject = new object();
object obj = new object();

// вызов расширения для object
obj.ExtensionMethod();

// вызов расширения для dynamic
ObjectExtensions.ExtensionMethod(dynamicObject);

Если писать так - то все работает. Но профита в методах расширения уже нет

Читать дальше...

Вызов методов с Generic параметрами через Reflection

Допустим, есть метод с Generic-параметром

public void GenericMethod<T>()
{
}

и есть метод с параметром типа Type

public void TypeMethod(Type type)
{
}

Вызвать TypeMethod из GenericMethod не составляет труда:

public void GenericMethod<T>()
{
TypeMethod(typeof(T));
}

А как вызвать GenericMethod из TypeMethod?
Поможет Reflection


public static object InvokeGenericMethod(object obj, string methodName, Type genericParam, params object[] pars)
{
var methodInfo = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
var method = methodInfo.MakeGenericMethod(new[] { genericParam });

return method.Invoke(obj, pars);
}

С помощью этого статического метода можно вызвать любой метод любого класса с одним Generic-параметром. Ну и если Generic-параметров больше:

public static object InvokeGenericMethod(object obj, string methodName, Type[] genericParams, params object[] pars)
{
var methodInfo = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
var method = methodInfo.MakeGenericMethod(genericParams);

return method.Invoke(obj, pars);
}

Пример использования:

public void GenericMethod<T>(int param1, object param2)
{
}

public void TypeMethod(Type type, int param1, object param2)
{
InvokeGenericMethod(this, "GenericMethod", type, param1, param2);
}


Читать дальше...

пятница, 23 сентября 2011 г.

АОП на .NET без Unity

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

Рассматривались три варианта решения:

1. Транспарентные прокси
Суть метода в том, что посредством Reflection мы создаем новый тип, который наследуется от нужного интерфейса и реализует прозрачный прокси-класс, выполняющий аспекты перед вызовом.
Этот метод посредством контейнера Unity я попробовал применить на живом проекте и у меня осталась масса впечатлений :)
http://fullfeedbag.blogspot.com/2010/09/aop-application-unity.html


Плюсы: после некоторых мучений АОП все же работает

Минусы: как по мне, то их оочень много
Самая большая беда: если что то сделать не так - аспекты могут отвалиться. Это не сильно страшно скажем для логирования. Но вот если на аспектах построен контроль синхронизации потоков, механизмы валидации или уровни доступа при авторизации, то отвалившиеся аспекты сделают целевую систему жутко уязвимой.
А сделать что то не так, к сожалению, очень даже легко, поскольку транспарентный прокси генерируется внешним компонентом, и если случайно его создать скажем с помощью оператора new - то аспекты не работают.
Вторая проблема: упавшая производительность. Падает она действительно очень сильно
Третья проблема: отладка кода сильно затрудняется. Отладчик Visual Studio неохотно ест сгенерированные в runtime типы данных
Четвертая проблема: багоемкость и высокий порог понимания для программистов. Объяснить принципы работы транспарентных прокси новому человеку, пришедшему на проект, ох как непросто. В совокупности с отваливающимися аспектами и диковатой отладкой это превращается в сущий ад


2. Модификация сборок в рантайме
Вообще, эта идея мне пришла сразу как только я начал применять АОП :) Мы просто берем и перехватываем метод класса. Без всяких прокси и прочей мути.
Один хороший человек разработал библиотечку для перехвата методов
http://www.codeproject.com/KB/dotnet/CLRMethodInjection.aspx
Если чуть пораскинуть мозгами - то ее можно применить для реализации АОП

Плюсы: решается проблема с быстродействием. Отваливания аспектов при грамотной реализации интерцептора не будет

Минусы:
1. Отладка еще хуже чем в случае с транспарентными прокси
2. Из-за того что метод использует грязные хаки чуть менее чем полностью, он очень сильно завязывается на версии .net framework и на его внутренних структурах данных. Проблема при переносе кода x32/x64. Если что то не так - программа падает
В общем для коммерческого проекта такой метод применять не очень хочется. Но сам по себе он очень интересен. Вот еще годные линки по теме покопаться в недрах дотнета:
http://www.codeproject.com/KB/dotnet/DotnetInternals_Injection.aspx
http://msdn.microsoft.com/en-us/magazine/cc163791.aspx
http://ntcore.com/files/disasmsil.htm


3. Модификация сборок до загрузки с помощью Mono.Cecil
Ахтунг! Не смотря на название либы Mono.Cecil, к самому Mono она, по видимому, не имеет отношения


Замысел в том, чтобы пофиксать сборочку один раз на диске и прикрутить к ней почти полноценное АОП. Библиотека Mono.Cecil позволит нам модифицировать сборки не копаясь в памяти и во внутренней структуре.


Плюсы: такие же как у всех - работающее АОП в результате
По поводу отладки затрудняюсь сказать (не пробовал), но думаю что с ней такие же проблемы как и у предыдущих методов


Минусы:
1. Фиксап подписанной (signed) сборки приводит к слетанию ЭЦП и неработоспособности сборки (кстати предыдущий метод был лишен такой проблемы).
Лечится тем, что нужно запустить процедуру фиксапа сразу после компиляции, ДО операции подписывания DLL. Это подразумевает то, что DLL своего производства. С чужими DLL такое не прокатывает

2. Операцию инжекции аспектов в сборку нужно запускать отдельно от кода. Если ее случайно не запустить - ошибка не выдается, аспекты не работают.
Введение этапа посткомпиляции - достаточно некрасивое решение. Хотя что то подобное сделали для прекомпиляции в Qt (MOC), так что может не все так плохо?

У этого метода есть приличные готовые реализации:
http://habrahabr.ru/blogs/net/95211/


Мораль сей басни состоит в том, что таки-нету идеальной реализации АОП на .NET
Так что ждем поддержки средств АОП от Microsoft на уровне компилятора. Ну или хотя бы stable версию АОП-фреймверка с Mono.Cecil


P.S. Я забыл упомянуть о PostSharp. Он использует и транспарентные прокси, и модификацию IL кода. Но, блин, стОит он дороговато...

P.P.S Литература по теме применения аспектно-ориентированного программирования:
http://www.javable.com/columns/aop/workshop/01/
http://www.javable.com/columns/aop/workshop/02/
http://citforum.ru/internet/javascript/aop/

Читать дальше...

пятница, 1 апреля 2011 г.

Длинные пути файловой системы в .NET

Любимая всеми ОС Windows с не менее любимой ФС NTFS может поддерживать пути длинной до 32К символов. Однако  мало кто этим воспользовался, поскольку в обычной жизни DOS-пути (а ими являются все пути вида C:\Windows...) поддерживают всего лишь 260 символов. Частенько этого бывает мало. Скажем, необходимо восстановить инфраструктуру сайта, а пути там могут быть куда длиннее.
В таком случае хорошо бы воспользоваться обещанными 32К-длинными путями. Но вот незадача: при попытке сделать путь хотя бы немного длиннее дозволенного, мы схлопочем исключение:
The path is too long after being fully qualified. Make sure path is less than 260 characters.
Для использования длинных путей с помощью WinAPI делают так:
1. Используют UNICODE-версии функций: CreateFileW, CreateDirectoryW и т.д.
2. Формируют путь в стиле NT: "\\?\C:\Windows..."

Еще одна проблема в том, что пути NT никак не модерируются системой: если обычные DOS пути можно писать как попало (C:/Windows\\\\\system32), и система спокойно съест такое, то для NT путей это недопустимо. То есть вся ответственность за корректные пути ложится на программиста.
Вернемся к дотнету. Он, конечно же, не предоставит нам выбора какими функциями пользоваться для работы с файлом. Придется использовать P/Invoke.
Проблема не нова, и решения можно поискать например здесь.
Я же предлагаю сконцентрироваться на устранении последствий применения P/Invoke, которое:
а. Поломает архитектуру
б. Сделает программу ОС-зависимой

Пункт (б) побороть никак не получится (кроме как использовать условную компиляцию). А вот архитектуру надо бы сохранить. Если у вас готова уже половина кода проекта, то переписывать куски с учетом использования нестандартного механизма работы с файлами совсем не хочется. Поэтому напишем класс, который будет совместим с обычным FileStream, но будет работать с длинными путями файловой системы.
Для сохранения архитектуры (конечно если она построена на принципах dependency injection) достаточно будет заменить фабрики, создающие объект файла или внести поправки в инициализацию DI контейнера.

Итак, ближе к делу. Сначала подготовим статический класс WinApi, в котором импортируем функции из kernel32.dll и определим константы. Также внимание надо уделить нормализации путей (ее сюда же добавим):
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace WindowsSpecific
{
    public static class WinApi
    {
        #region ECreationDisposition enum

        public static string NormalizePath(string path)
        {
            const int bufferSize = 32767;

            if (path.StartsWith(".") || !path.Contains(":"))
            {
                var stringBuilder = new StringBuilder {Length = bufferSize};
                GetCurrentDirectory(bufferSize-1, stringBuilder);

                path = stringBuilder + "/" + path.TrimStart(new[] { '.' });
            }

            path = path.Replace('/', '').Trim();
           
            while (path.Contains(@""))
                path = path.Replace(@"", @"");

            return @"?" + path;
        }

        public const uint ErrorAlreadyExist = 0x000000b7;

        public enum ECreationDisposition : uint
        {
            New = 1,
            CreateAlways = 2,
            OpenExisting = 3,
            OpenAlways = 4,
            TruncateExisting = 5,
        }
        #endregion

        #region EFileAccess enum
        [Flags]
        public enum EFileAccess : uint
        {
            GenericRead = 0x80000000,
            GenericWrite = 0x40000000,
            GenericExecute = 0x20000000,
            GenericAll = 0x10000000,
        }
        #endregion

        #region EFileAttributes enum
        [Flags]
        public enum EFileAttributes : uint
        {
            Readonly = 0x00000001,
            Hidden = 0x00000002,
            System = 0x00000004,
            Directory = 0x00000010,
            Archive = 0x00000020,
            Device = 0x00000040,
            Normal = 0x00000080,
            Temporary = 0x00000100,
            SparseFile = 0x00000200,
            ReparsePoint = 0x00000400,
            Compressed = 0x00000800,
            Offline = 0x00001000,
            NotContentIndexed = 0x00002000,
            Encrypted = 0x00004000,
            Write_Through = 0x80000000,
            Overlapped = 0x40000000,
            NoBuffering = 0x20000000,
            RandomAccess = 0x10000000,
            SequentialScan = 0x08000000,
            DeleteOnClose = 0x04000000,
            BackupSemantics = 0x02000000,
            PosixSemantics = 0x01000000,
            OpenReparsePoint = 0x00200000,
            OpenNoRecall = 0x00100000,
            FirstPipeInstance = 0x00080000
        }
        #endregion

        #region EFileShare enum
        [Flags]
        public enum EFileShare : uint
        {
            None = 0x00000000,
            Read = 0x00000001,
            Write = 0x00000002,
            Delete = 0x00000004,
        }
        #endregion

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern SafeFileHandle CreateFile
            (
            string lpFileName,
            EFileAccess dwDesiredAccess,
            EFileShare dwShareMode,
            IntPtr lpSecurityAttributes,
            ECreationDisposition dwCreationDisposition,
            EFileAttributes dwFlagsAndAttributes,
            IntPtr hTemplateFile
            );

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool CreateDirectory
            (
            string lpFileName,
            IntPtr lpSecurityAttributes
            );

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool CopyFile(
          string lpExistingFileName,
          string lpNewFileName,
          bool bFailIfExists
        );

        [DllImport("kernel32.dll")]
        public static extern uint GetCurrentDirectory(uint nBufferLength,
           [Out] StringBuilder lpBuffer);

        public static ECreationDisposition ECreationDispositionByFileMode(FileMode fileMode)
        {
            var winapiMode = ECreationDisposition.OpenAlways;
            switch (fileMode)
            {
                case FileMode.CreateNew:
                    winapiMode = ECreationDisposition.New;
                    break;

                case FileMode.Create:
                    winapiMode = ECreationDisposition.CreateAlways;
                    break;

                case FileMode.Open:
                    winapiMode = ECreationDisposition.OpenExisting;
                    break;

                case FileMode.OpenOrCreate:
                    winapiMode = ECreationDisposition.OpenAlways;
                    break;

                case FileMode.Truncate:
                    winapiMode = ECreationDisposition.TruncateExisting;
                    break;

                case FileMode.Append:
                    winapiMode = ECreationDisposition.OpenAlways;
                    break;
            }
            return winapiMode;
        }

        public static EFileAccess EFileAccessByFileAccess(FileAccess fileAccess)
        {
            var winapiAccess = EFileAccess.GenericAll;

            switch (fileAccess)
            {
                case FileAccess.Read:
                    winapiAccess = EFileAccess.GenericRead;
                    break;

                case FileAccess.Write:
                    winapiAccess = EFileAccess.GenericWrite;
                    break;

                case FileAccess.ReadWrite:
                    winapiAccess = EFileAccess.GenericRead | EFileAccess.GenericWrite;
                    break;
            }

            return winapiAccess;
        }
    }
}

Теперь напишем класс, перегрузив FileStream:
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace WindowsSpecific
{
    public class LongPathFileStream : FileStream
    {
        private static SafeFileHandle Initialize(string fileName, FileMode fileMode, FileAccess fileAccess)
        {
            var longFileName = WinApi.NormalizePath(fileName);

            var fileHandle = WinApi.CreateFile(longFileName,WinApi.EFileAccessByFileAccess(fileAccess),
                WinApi.EFileShare.Read,IntPtr.Zero,WinApi.ECreationDispositionByFileMode(fileMode),0,IntPtr.Zero);

            var lastWin32Error = Marshal.GetLastWin32Error();
            if (fileHandle.IsInvalid)
                throw new System.ComponentModel.Win32Exception(lastWin32Error);

            return fileHandle;
        }

        public LongPathFileStream(string fileName)
            :base(Initialize(fileName,FileMode.OpenOrCreate,FileAccess.ReadWrite),FileAccess.ReadWrite)
        {
        }

        public LongPathFileStream(string fileName,FileAccess fileAccess)
            :base(Initialize(fileName,FileMode.OpenOrCreate,fileAccess),fileAccess)
        {
        }

        public LongPathFileStream(string fileName, FileMode fileMode, FileAccess fileAccess)
            :base(Initialize(fileName,fileMode,fileAccess),FileAccess.ReadWrite)
        {
            if (fileMode == FileMode.Append)
                AppendMode();
        }

        private void AppendMode()
        {
            Position = Length;
        }
    }
}

Вот и все! Теперь если вместо FileStream задействовать LongPathFileStream, то можно создавать и открывать файлы используя путь длинной до 32К символов.

Есть еще правда вот такая проблема. Если создать файл, путь к которому длиннее 260 символов, то прочитать или удалить такой файл можно лишь программами, которые умеют работать с длинными путями, а таких куда меньше чем хотелось бы. Либо воспользоваться кривыми приемами от Microsoft (читаем тут, параграф 4).

Для полноты картины нужно бы обернуть еще WinAPI функции для создания и удаления директории, поиск файлов. Но это в другой раз.



Читать дальше...

четверг, 31 марта 2011 г.

Паттерн Service Locator на C++

Паттерн Service locator представляет собой хранилище сервисных объектов. Фактически это некоторого рода ассоциативный массив с экземплярами объектов-реализаций, которые определяются по ключу – типу или имени интерфейса.

Применяется для внедрения зависимостей (Dependency Injection). В целом очень полезная фича. Иногда этот паттерн реализуется DI контейнерами заодно с основной функциональностью.

Алгоритм работы с Service Locator:
1. Регистрация экземпляров реализаций сервисных объектов в ServiceLocator путем ассоциирования с типом или именем интерфейса
2. Передача экземпляра ServiceLocator в классы, которым необходимы сервисные объекты
3. Класс, которому необходим сервисный объект, запрашивает его по ключу у контенера

Получаем развязку интерфейса и реализации.

Спроектируем и реализуем Service Locator на C++.

Терминология:
Сервис – экземпляр класса, реализующего интерфейс
Интерфейс сервиса – класс, который декларирует методы взаимодействия с сервисом, но не реализовывает их
Тег – строковой идентификатор объекта в контейнере



Актерами выступают:
- конфигуратор – функционал, создающий ServiceLocator и настраивающий сервисы
- пользователь – функционал, использующий сервисы

Конфигуратор может:
- создать ServiceLocator
- регистрировать ассоциацию типа интерфейса с экземпляром объекта сервиса с указанием тега
- регистрировать ассоциацию типа интерфейса с экземпляром объекта сервиса без указания тега

Пользователь может:
- получать сервис по запросу типа интерфейса с указанием тега
- получать сервис по запросу типа интерфейса без указания тега
- получать сервис по запросу имени типа интерфейса с указанием тега
- получать сервис по запросу имени типа интерфейса без указания тега

Реализация

Интерфейс IServiceLocator

#include <string>
#include <list>

class IServiceLocator
{
public:
virtual ~IServiceLocator() {};

virtual void RegisterService(std::string name, void* serviceObject, std::string tag = "") = 0;
virtual void* Locate(std::string name, std::string tag = "") = 0;

template<typename T>
void RegisterService(T* serviceObject, std::string tag = "")
{
RegisterService(typeid(T).name(), reinterpret_cast<void*>(serviceObject), tag);
}

template<typename T>
T* Locate(std::string tag = "")
{
return (T*)Locate(typeid(T).name(),tag);
}
};


Декларация реализации ServiceLocator


#include <list>
#include <string>

class ServiceLocator : public IServiceLocator
{
public:
virtual void RegisterService(std::string name, void* serviceObject, std::string tag = "");
virtual void* Locate(std::string name, std::string tag = "");
virtual ~ServiceLocator();

protected:
struct Service
{
void* ServiceObject;
std::string Name;
std::string Tag;
};

private:
std::list<struct Service> services_;
typedef std::list<struct Service>::iterator ServiceIterator;
};


Реализация ServiceLocator

ServiceLocator::~ServiceLocator()
{
for (ServiceIterator i = services_.begin(); i != services_.end(); i++)
{
if (i->ServiceObject != NULL)
{
delete i->ServiceObject;
i->ServiceObject = NULL;
}
}
services_.clear();
}

void ServiceLocator::RegisterService(std::string name, void* serviceObject, std::string tag)
{
if (serviceObject == NULL)
throw std::invalid_argument("serviceObject");

for (ServiceIterator i = services_.begin(); i != services_.end(); i++)
{
if ((i->Name == name) && (i->Tag == tag))
{
throw std::runtime_error(std::string("Duplicate service registration for '") + name +
std::string("' and tag '") + tag + std::string("'"));
}
}

struct Service service;
service.Name = name;
service.ServiceObject = serviceObject;
service.Tag = tag;

services_.push_back(service);
}

void* ServiceLocator::Locate(std::string name, std::string tag)
{
for (ServiceIterator i = services_.begin(); i != services_.end(); i++)
{
if ((i->Name == name) && (i->Tag == tag))
{
return reinterpret_cast<void*>(i->ServiceObject);
}
}
throw std::runtime_error(std::string("Service not found for '") + name +
std::string("' and tag '") + tag + std::string("'"));
}


Пример использования:

IServiceLocator *serviceLocator = new ServiceLocator();

serviceLocator->RegisterService<IService>(new ServiceRelease());
serviceLocator->RegisterService<IService>(new OtherServiceRelease(),"svc");

IService* service = serviceLocator->Locate<IService>();
IService* otherService = serviceLocator->Locate<IService>("svc");


Читать дальше...

среда, 30 марта 2011 г.

Валидация параметров конструктора класса в секции инициализации

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


MyClass::MyClass (A* a, B* b)
{
if (!a) throw std::invalid_argument("a");
if (!b) throw std::invalid_argument("b");
a_ = a;
b_ = b;
}

Красивее (да и правильнее) было бы выполнять присвоение в секции инициализации:

MyClass::MyClass (A* a, B* b)
:a_(a), b_(b)
{
if (!a) throw std::invalid_argument("a");
if (!b) throw std::invalid_argument("b");
}

Недостатки налицо: некрасиво и не очень функционально. Присвоение выполняется еще до проверок. Кроме того, бывает еще такая ситуация:


class MyClass
{
A* a_;
B& b_;

MyClass(A* a)
:b_(a->getRefB())
{
}
}

Проблема в том, что если в качестве параметра будет передан невалидный указатель, то будет сгенерировано системное исключение еще до вызова конструктора. А переносить инициализацию внутрь конструктора нельзя: ссылка может быть проинициализирована только в секции инициализации класса.
Возможный выход в том, чтобы не попадать в подобную ситуацию. То есть – не инициализировать ссылку через указатель на объект. Но если мы хотим использовать Dependency Injection вместе со средствами C++ на полную катушку, то такая ситуация рано или поздно возникнет.
Идея состоит в том, чтобы проверить валидность указателя именно в секции инициализации. Помогут функции с шаблонными параметрами.

Итак, возможные ошибки с параметрами:
- передан нулевой указатель
- передан указатель на объект несовместимого типа
- передан «мусор»: значение, не являющееся указателем вовсе
Для проверки на соответствие указателя ожидаемому типу будем использовать RTTI.
Напишем функции с шаблонным параметром, которые смогут проверить указатель на ошибки:


template<typename T>
T NotNullChecker (T value, std::string variableName)
{
if (value == NULL)
throw std::invalid_argument(variableName + " cannot be NULL");

return value;
}

template<typename T>
T ConvertableTypeChecker(T value, std::string variableName)
{
if (dynamic_cast<T>(value) == NULL)
throw std::invalid_argument(variable + " must be convertible to type " + std::string(typeid(T).name()));

return value;
}

Для удобства допишем пару макросов для автоматической подстановки имен параметров:


#define NotNull(p) NotNullChecker(p,#p)
#define ConvertableType(p) ConvertableTypeChecker(p,#p)
#define ValidArgument(p) ConvertableTypeChecker(NotNullChecker(p,#p),#p)

Использование выглядит очень лаконично на мой взгляд:

class MyClass
{
A* a_;
B& b_;

MyClass(A* a)
:b_(ValidArgument(a) ->getRefB())
{
}
}

Вот так можно проверить аргумент на валидность еще в секции инициализации. Если развить идею дальше – можно написать таким же образом функцию проверки целочисленного аргумента на попадание в допустимый диапазон и т.д.

Читать дальше...