2013-06-21 10 views
3

인스턴스 메서드 또는 람다 함수가 필요하거나 scandir에 select 함수 인수로 전달되는 것과 같습니다. 이 일을 할 수있는 방법이 있습니까?C++ 11에서 scandir에 람다 함수를 전달하는 것은 아마도 불가능합니다. 다음으로 가장 좋은 것은 무엇입니까?

내가 얻으려고하는 핵심 기능은 select 함수 (콜백)를 호출하는 클래스의 각 인스턴스마다 다른 매개 변수를 보는 것입니다. 이 스레드로부터 안전하게 만들거나 끔찍하게 못생긴 변수로 만들려면 전역 변수에 매개 변수를 저장하면 안됩니다. 클래스 인스턴스는 전역 변수에 저장됩니다.

여기 그것이 11 C++로 람다 함수와 함께 일 경우처럼 보일 것입니다 무엇 :

myclass:getFilesMatching(char startChar) 
{ 
    ... 
    mParam = startChar; 
    auto lfunc = [this] (const struct dirent * dent) { return (*(dent->d_name) == mParam); }; 
    mNumFiles = scandir((char *)fullDirPath, &mfileList, lfunc, NULL); 
} 

이 이름이 지정된 문자로 시작하는 모든 파일을 얻을 것입니다. 함수에 지역 변수 또는 인스턴스 변수를 전달하더라도 상관하지 않습니다.

scandir 자체가 스레드 세이프가되기를 바랍니다. 물론 세마포어 나 뮤텍스를 사용할 수는 있지만 정말 필요합니까?

물론 이것은 선택 기능의 간단한 예일뿐입니다. 내가 실제로하고 싶은 것은 더욱 복잡합니다.

답변

3

나는 scandir 또는 관련 C 기능에 대한 사전 지식이 없다는 것을 인정해야합니다. 그러나 내가 읽은 내용을 <dirent.h>에있는 문서를 통해 이해하면 저수준 APIS로 여러 호출을 래핑하는 도우미 함수입니다.

이러한 경우 나는 C++과 같은 기능을 가진 동일한 기능을 구현하는 C++ 래퍼를 만드는 것을 선호합니다. 이 같은

namespace cpp { 

    struct DIR { 
     ::DIR * dir; 

     DIR(const std::string & path) : dir(opendir(path.c_str())) 
     { 
      if(dir==0) throw std::runtime_error("Unable to open path"); 
     } 

     ~DIR() { if(dir!=0) closedir(dir); } 

     operator ::DIR *() { return dir; } 
    }; 
} 

scandir 기능은 현재 구현 될 수있는 일 :

template<class Filter, class Compare> 
std::vector<dirent> scandir(const std::string & path, const Filter& filter, const Compare& compare) { 
    cpp::DIR dir(path); 
    std::vector<dirent> res; 
    dirent entry, *entry_ptr = &entry; 
    while(::readdir_r(dir, &entry, &entry_ptr)==0) { 
     if(entry_ptr==0) break; 
     if(filter(entry)) res.push_back(entry); 
    } 

    std::sort(begin(res), end(res), compare); 

    return res; 
} 

과 같이 호출 제대로 정리되었는지 확인하기 위해 DIR 유형의 래퍼로 시작

이 :

std::string path = ...; 
... 
auto filter = [] (const dirent& entry) { return entry.d_name[0]!='.'; }; 
auto compare = [](const dirent & lhs, const dirent & rhs) { 
    return std::strcmp(lhs.d_name, rhs.d_name)<0; 
}; 

auto entries = cpp::scandir(path, filter, compare); 

readdir_r()은 스레드로부터 안전하지 않은 구현을 만듭니다.하지만 더 많은 검사가 추가되어 오류에 의해 반환 된 오류를보고해야합니다.

참고 :

void with_file_names(const std::string &path, 
        std::function<void(const std::string& name)> action, 
        int(*filter)(const struct dirent *) = NULL) 
{ 
    struct dirent **namelist; 
    auto nfiles = scandir(path.c_str(), 
          &namelist, 
          filter, 
          NULL); 
    for (auto idx = 0; idx < nfiles; idx++) 
    { 
     auto n = namelist[idx]; 
     action(std::string(n->d_name, n->d_namlen)); 
    } 
    free(namelist); 
} 

다음

with_file_names("./downloaded", 
       [](const std::string &file_name){ 
        auto fullname = std::string("./downloaded/") + file_name; 
        // do something... 
       }, 
       [](const struct dirent *pentry) { 
        return (int)(::strncmp("node-", pentry->d_name, 5) == 0); 
       }); 

통지 필터 매개 변수를 :

#include <dirent.h> 
#include <cstring> 
#include <algorithm> 
#include <iostream> 
#include <stdexcept> 
#include <vector> 
2

이 내가 할 것입니다 : 위의 코드는 다음과 같은 헤더를 포함 할 필요 람다로서. 희망이 도움이됩니다!

+1

질문의 요점을 놓쳤다 고 생각합니다. 람다 함수를 사용하여 필터 함수에 인수를 전달할 수는 없으므로, 실제로 도움이되지 않습니다. –

+0

@ Étienne 그것은 재미 있습니다. 왜냐하면 _almost_가 도움이되기 때문입니다. 그는 비 캡처 람다 (실제로는 함수 포인터로 변환 될 수 있기 때문에 'scandir'의 이론적으로 유효한 피연산자입니다)를 사용하지만 대담 자로'std :: function'을 사용합니다 그 가능성을 완전히 없애 버린다. 'std :: function'을'std :: add_pointer_t' ('type_traits')로 변경하면 실제로 작동 할 수 있습니다 ('scandir '과 비슷한 것을 얻었습니다). – fish2000

+0

수정 된 버전의 FileZilla에서 사용 했으므로 설명대로 작동합니다. –

1

나는 또한 람다 식으로 람다 식으로 결혼하려했는데 흥미로운 결과가 나왔다. 다음 코드에서 작동 가능한 실행 파일을 생성 할 수있는 허점 (아마도 버전 및 컴파일러 관련 버그) gcc (데비안 4.9.2-10).당신이 그 (것)들을 차단 - 지역 만들기, 캡처리스트 변수의 범위 지정을 수정하려고하면

#include <iostream> 
#include <dirent.h> 
#include <cstring> 
using std::cout; 
using std::endl; 

char *ext; 
unsigned int lext; 

int main (int argc, char **argv) 
{ 
    struct dirent **listing; 
    ext = argv[2]; 
    lext = strlen (ext); 
    int n = scandir (argv[1], 
        &listing, 
        [ext , lext] (const struct dirent *ent) -> int 
        { 
         // Select only files with extension EXT 
         auto lon = strlen (ent->d_name); 
         return (lon > lext and 
           !strcmp (ent->d_name + (lon-lext), ext)) 
          ? 1 : 0; 
        }, 
        alphasort); 
    if (n==-1) 
    { 
     cout << "scandir has failed" << endl; 
     return 1; 
    } 
    else { 
     cout << "scandir has found "<< n <<" matching entries.\n"; 
     for (int i=0 ; i<n ; ++i) 
      cout << i+1 << " - "<< listing[i]->d_name << "\n"; 
     return 0; 
    } 
} 

, 다음과 같은 오류와 함께 컴파일을 거부합니다

error: cannot convert ‘main(int, char**)::’ to ‘int ()(const dirent)’ for argument ‘3’ to ‘int scandir(const char*, dirent***, int ()(const dirent), int (*)(const dirent**, const dirent**))’

이는 것을 의미하는 것 가능 함수에 람다를 컴파일하기 때문에 포인터가 작동하는 방식으로 scandir에 주어질 수 있습니다. 그러나 나는 다른 두 가지 대답의 C++ 접근법을 선호한다.

이것이 컴파일러 버그로 간주되어야하는지 잘 모르겠습니다.

+1

예 - 비 캡처 람다 만 함수 포인터로 변환 할 수 있습니다. 컴파일러 버그가 아닙니다. q.v. http://en.cppreference.com/w/cpp/language/lambda – fish2000