结合此question。对于以下看似基本的问题,我想不出一个好的类型安全的解决方案。我有一个music_playlist类,其中包含应播放的歌曲列表。似乎很简单,只需对队列中的所有歌曲添加一个std::list,然后将其提供给用户即可。但是,出于必要,音频解码和音频渲染发生在单独的线程上。因此,该列表需要互斥保护。通常,使用我的库的其他程序员常常忘记了互斥锁。这显然导致了“奇怪”的问题。
因此,起初,我只是为该类(class)写了二传手。

struct music{};
class music_playlist{
private:
     std::list<music> playlist;
public:
     void add_song(music &song){playlist.push_back(song);}
     void clear(){playlist.clear();}
     void set_list(std::list<music> &songs){playlist.assign(songs.begin(),songs.end());}
//etc
};
这导致了如下用户代码...
music song1;
music song2;
music song3;
music song4;
music song5;
music song6;
music_playlist playlist1;
playlist1.add_song(song1);
playlist1.add_song(song2);
playlist1.add_song(song3);
playlist1.add_song(song4);
playlist1.add_song(song5);
playlist1.add_song(song6);

//or
music_playlist playlist2;
std::list<music> songs;
songs.push_back(song1);
songs.push_back(song2);
songs.push_back(song3);
songs.push_back(song3);
songs.push_back(song5);
songs.push_back(song6);
playlist2.set_list(songs);
虽然这有效并且非常明确。键入非常繁琐,并且由于实际工作中的所有麻烦而容易出错。为了说明这一点,我实际上是有意在上面的代码中放入了一个错误,类似这样的事情很容易实现,并且很可能会经历未审查的代码审查,而song4却不会在播放列表2中播放。
从那里,我开始研究可变函数。
struct music{};
class music_playlist{
private:
     std::list<music> playlist;
public:
     void set_listA(music *first,...){
     //Not guaranteed to work, but usually does... bleh
         va_list Arguments;
    va_start(Arguments, first);
    if (first) {
        playlist.push_back(first);
    }
    while (first) {
        music * a = va_arg(Arguments, music*);
        if (a) {
            playlist.push_back(a);
        }else {
            break;
        }
    }
    }
    void set_listB(int count,music first,...){
         va_list Arguments;
     va_start(Arguments, first);
     playlist.push_back(first);

    while (--count) {
        music a = va_arg(Arguments, music);
            playlist.push_back(a);
    }
    }
//etc
};
这将导致如下所示的用户代码...
playlist1.set_listA(&song1,&song2,&song3,&song4,&song5,&song6,NULL);
//playlist1.set_listA(&song1,&song2,&song3,&song4,&song5,&song6); runtime error!!
//or
playlist2.set_listB(6,song1,song2,song3,song4,song5,song6);
现在,更容易查看歌曲是否被添加了两次或不包括在内。但是,在解决方案A中,如果NULL不在列表的末尾并且不是跨平台的,它将崩溃。在solutionB中,您必须计算可能导致多个错误的参数数量。同样,所有解决方案都不是类型安全的,用户可以传递不相关的类型并在运行时等待崩溃。这似乎不是一个可持续的解决方案。因此,我遇到了std::initializer_list。无法使用它我开发的一些编译器尚不支持它。所以我试图模仿它。我最终在下面找到了这个解决方案。
Is this use of the "," operator considered bad form?
这将导致用户代码如下所示...
struct music_playlist{
    list<music> queue;
//...
};
int main (int argc, const char * argv[])
{
    music_playlist playlist;
    music song1;
    music song2;
    music song3;
    music song4;
    playlist.queue = song1,song2; // The queue now contains song1 and song2
    playlist.queue+= song1,song3,song4; //The queue now contains two song1s and song2-4
    playlist.queue = song2; //the queue now only contains song2
    return 0;
}
这种语法不会使我们的小型测试组感到困惑。但是,我对如此严重地滥用运算符(operator)重载有严重的担忧。所以我在上面发布了问题。我想看看那些是我们测试小组的专家的程序员。相当多的程序员不喜欢它,但是它似乎比上面的解决方案更好,因为它会在编译时而不是在运行时捕获大多数错误。但是,汤姆发布了一个有趣的反例,这将导致意外行为。
//lets combine differnt playlists
new_playlist.queue =    song1        //the first playlist
                      ,(song3,song4) //the second playlist //opps, I didn't add song 3!
                      , song5;
这使我感到难以解决。那么您对更好的解决方案有什么想法吗?
P.S.以上代码均未编译,仅出于示例目的。

最佳答案

我想到的第一个问题是这是否是一个问题,是否值得解决。由于您正在创建界面,因此您的客户端将是用户代码(而不是人员代码)。您的客户会在代码中硬编码播放列表几次,而不是说从文件中存储加载列表,或从用户选择中构造它们?

考虑该功能的实际值(value),并将其与使用它们对您的库的影响进行比较,并考虑您的用户在更改界面后可能会遇到多少问题。

最简单的解决方案是接受迭代器来构造/重置列表,然后让用户处理该问题。当然,他们可以按照您所示的方式构建自己的容器,但是他们也可以使用Boost.Assignment来处理样板,因此他们的用户代码如下所示:

std::vector<music> songs = boost::assign::list_of()( song1 )( song2 );
play_list.set( songs.begin(), songs.end() );

或者,如果他们不满意该库,则可以使用普通的旧数组:
music songs[2] = { song1, song2 };
play_list:set( songs, songs + 2 ); // add your preferred magic to calculate the size

关于c++ - 什么是C++中可变参数函数的良好类型安全替代品?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/6729977/

10-11 19:40
查看更多