我如何重构这个以使用内联函数或模板而不是宏?

时间:2010-03-22 04:26:50

标签: c++ macros

我在这里有一个有用的宏:

#include <algorithm>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
#include <Windows.h>

namespace Path {

bool Exists(const std::wstring& path)
{
    DWORD result = GetFileAttributesW(path.c_str());
    return result != INVALID_FILE_ATTRIBUTES;
}

// THIS IS THE MACRO IN QUESTION!
#define PATH_PREFIX_RESOLVE(path, prefix, environment) \
if (boost::algorithm::istarts_with(path, prefix)) { \
    ExpandEnvironmentStringsW(environment, buffer, MAX_PATH); \
    path.replace(0, (sizeof(prefix)/sizeof(wchar_t)) - 1, buffer); \
    if (Exists(path)) return path; \
}

std::wstring Resolve(std::wstring path)
{
    using namespace boost::algorithm;
    wchar_t buffer[MAX_PATH];
    trim(path);
    if (path.empty() || Exists(path)) return path;

    //Start by trying to see if we have a quoted path
    if (path[0] == L'"') {
        return std::wstring(path.begin() + 1, std::find(path.begin() + 1, path.end(), L'"'));
    }

    //Check for those nasty cases where the beginning of the path has no root
    PATH_PREFIX_RESOLVE(path, L"\\", L"");
    PATH_PREFIX_RESOLVE(path, L"?\?\\", L"");
    PATH_PREFIX_RESOLVE(path, L"\\?\\", L"");
    PATH_PREFIX_RESOLVE(path, L"globalroot\\", L"");
    PATH_PREFIX_RESOLVE(path, L"system32\\", L"%systemroot%\\System32\\");
    PATH_PREFIX_RESOLVE(path, L"systemroot\\", L"%systemroot%\\");

    static std::vector<std::wstring> pathExts;
    if (pathExts.empty()) {
        #define MAX_ENVVAR 32767
        wchar_t pathext[MAX_ENVVAR];
        DWORD length = GetEnvironmentVariableW(L"PATHEXT", pathext, MAX_ENVVAR);
        if (!length) WindowsApiException::ThrowFromLastError();
        split(pathExts, pathext, std::bind2nd(std::equal_to<wchar_t>(), L';'));
        pathExts.insert(pathExts.begin(), std::wstring());
    }
    std::wstring::iterator currentSpace = path.begin();
    do {
        currentSpace = std::find(currentSpace, path.end(), L' ');
        std::wstring currentPath(path.begin(), currentSpace);
        std::wstring::size_type currentPathLength = currentPath.size();
        typedef std::vector<std::wstring>::const_iterator ExtIteratorType;
        for(ExtIteratorType it = pathExts.begin(); it != pathExts.end(); it++) {
            currentPath.replace(currentPathLength, currentPath.size() - currentPathLength, *it);
            if (Exists(currentPath)) return currentPath;
        }
        if (currentSpace != path.end())
            currentSpace++;
    } while (currentSpace != path.end());

    return path;
}

}

它在单个函数的范围内使用了大约6次(就是这样),但宏似乎有“坏业力”:P

无论如何,这里的问题是宏的sizeof(prefix)部分。如果我只是使用const wchar_t[]的函数替换它,那么sizeof()将无法提供预期的结果。

简单地添加一个size成员并不能真正解决问题。使用户提供常量文字的大小也会导致调用站点出现重复的常量。

关于这个的任何想法?

3 个答案:

答案 0 :(得分:3)

通过引用传递数组,使用模板推断长度。我会去寻找一个例子,但基本上是:

template<size_t N>
bool func(const char (&a)[N], blah, blah) { ... }
编辑:有人在这里解释: http://heifner.blogspot.com/2008/04/c-array-size-determination.html

答案 1 :(得分:3)

为什么不让它成为一个常规函数,使用wcslen()来获取你感兴趣的参数的长度。在那个宏/函数中有足够的东西,我想这里试图强迫它没什么价值它被内联。 '开销; wcslen()呼叫和处理几乎肯定不会成为瓶颈。

你应该关注的宏中唯一的技巧是(正如GMan指出的那样)从调用宏时隐藏的宏内部返回。

让事情成为一个返回成功/失败的函数,如果函数成功,你可以返回:

bool PathPrefixResolve( std::wstring& path, wchar_t const* prefix, wchar_t const* environment)
{
    wchar_t buffer[MAX_PATH];

    if (boost::algorithm::istarts_with(path, prefix)) {
        ExpandEnvironmentStringsW( environment, buffer, MAX_PATH);

        std::wstring tmp( path);

        tmp.replace(0, wcslen( prefix), buffer);
        if (Exists(tmp)) {
            path = tmp;
            return true;
        }
    }

    return false;
}

使用该功能:

//Check for those nasty cases where the beginning of the path has no root
if (PathPrefixResolve2(path, L"\\", L"")) return path;
if (PathPrefixResolve2(path, L"?\?\\", L"")) return path;
if (PathPrefixResolve2(path, L"\\?\\", L"")) return path;
if (PathPrefixResolve2(path, L"globalroot\\", L"")) return path;
if (PathPrefixResolve2(path, L"system32\\", L"%systemroot%\\System32\\")) return path;
if (PathPrefixResolve2(path, L"systemroot\\", L"%systemroot%\\")) return path;

考虑到宏中发生的处理,我认为你不必担心函数调用开销。

另外,你的宏实现有一些我认为可能是错误的行为 - 如果路径以L"\\?\\"开头,这意味着它也以L"\\"开头并且你第一次调用宏:

PATH_PREFIX_RESOLVE(path, L"\\", L"");

将更改path变量。随着程序得到维护并添加额外的前缀,可以在其他路径前缀中看到问题。此错误不在函数版本中,因为该函数仅在验证匹配时才更改路径参数。

但是,在处理L"\\?\\"L"\\"前缀时仍然可能存在一个问题,即两者都可能匹配 - 您需要确保传入可能匹配多次的前缀按'优先'顺序。

答案 2 :(得分:1)

您是否尝试将sizeof(prefix)/sizeof(wchar_t)替换为wcslen(prefix)